From 6994db438bd0977f1f9051627b8b0255af29629e Mon Sep 17 00:00:00 2001
From: Pavel 'LEdoian' Turinsky
Date: Thu, 5 Jan 2023 04:40:16 +0100
Subject: [PATCH 1/7] =?UTF-8?q?P=C5=99id=C3=A1n=C3=AD=20tagu=20{%=20mailli?=
=?UTF-8?q?nk=20%}?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Vyrábí odkazy, které vedou na poslání mailu.
Psal jsem to spíš po paměti, nejsem si jistý, že to takhle je přesně
podle příslušného RFC, ale jako PoC dobrý a když to nebude fungovat, tak
se implementace opraví.
Všimněte si, že to je otestované, takže když někdo opraví testy
(=předpis chování), tak je pak snadné z diffu a všeho odvodit úpravu.
V Django dokumentaci se píše něco o tom, že by se měl použít spíš
`format_html` a `conditional_escape`, ale zatím jsem to víc nezkoumal.
Je žádoucí z tagu {% maillink %} odddělit i tag {% mailurl %}, který by
vracel samotnou URL. Obojí dává smysl umět (speciálně bastlení odkazů z
URL je stejně strašně nepřehledné, takže je lepší to zavřít do {%
maillink %} a nikdy nevidět), ale zatím to oddělené není… (Ale jsou na
to testy, takže by se mělo aspoň dát poznat, že rozdělení nerozbije
chování.)
---
various/templatetags/__init__.py | 0
various/templatetags/mail.py | 30 ++++++++++++++++++++++
various/tests.py | 43 +++++++++++++++++++++++++++++++-
3 files changed, 72 insertions(+), 1 deletion(-)
create mode 100644 various/templatetags/__init__.py
create mode 100644 various/templatetags/mail.py
diff --git a/various/templatetags/__init__.py b/various/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/various/templatetags/mail.py b/various/templatetags/mail.py
new file mode 100644
index 00000000..fe11d218
--- /dev/null
+++ b/various/templatetags/mail.py
@@ -0,0 +1,30 @@
+from django import template
+from django.utils.safestring import mark_safe
+from urllib.request import quote as urlencode
+register = template.Library()
+
+@register.simple_tag
+def maillink(text: str, subject=None, body=None, to=[], attrs=None):
+ """TODO: Dokumentace"""
+ if isinstance(to, str):
+ to = [to]
+ assert isinstance(to, list)
+ parts = [
+ f'mailto:{str.join(",", to)}',
+ ]
+ if len(to) < 1:
+ raise ValueError('Cannot mail to empty set of people')
+
+ if subject:
+ parts.append(f'subject={urlencode(subject)}')
+ if body:
+ parts.append(f'body={urlencode(body)}')
+
+ if len(parts) > 1:
+ url = parts[0] + '?' + str.join('&', parts[1:])
+ else:
+ url = parts[0]
+ if not attrs: attrs = ''
+ mezera = ' '*bool(attrs)
+ full_link = f'{text}'
+ return mark_safe(full_link)
diff --git a/various/tests.py b/various/tests.py
index 7ce503c2..7884e618 100644
--- a/various/tests.py
+++ b/various/tests.py
@@ -1,3 +1,44 @@
from django.test import TestCase
+# TODO: Možná vyrobit separátní soubory v tests/… než mít všechny testy v jednom souboru?
+from various.templatetags.mail import maillink
-# Create your tests here.
+class MailTagsTest(TestCase):
+ """Testuje template tagy ohledně mailů."""
+ def test_maillink(self):
+ # Tohle nedává smysl dělit do víc funkcí, bylo by v nich víc boilerplatu než užitečného kódu.
+ self.assertEquals(maillink('Hello', to='some@body.test'), r'Hello')
+ self.assertEquals(maillink('Hello', to=['some@body.test']), r'Hello')
+ self.assertEquals(
+ maillink('Hello', to=['alice@test.test', 'bob@jinde.test']),
+ r'Hello',
+ )
+ self.assertEquals(
+ maillink('Hello', to='some@body.test', attrs='class="trida" id="id"'),
+ r'Hello',
+ )
+ # Následující test toho testuje moc zároveň, měly by předcházet dedikované testy… (kašlu na ně :-P)
+ self.assertEquals(
+ maillink('Text odkazu', to='prijemce@wtf.test', subject="Předmět", body="Čau"),
+ r'Text odkazu',
+ )
+ self.assertRaises(ValueError, lambda: maillink('Nemám příjemce'))
+ self.assertRaises(TypeError, lambda: maillink()) # Nemá text, takže to shodí python
+
+ def test_render_in_template(self):
+ # Pomocná funkce: vykreslí template do stringu
+ # Ref: https://stackoverflow.com/a/1690879
+ def render_template(template, context=None):
+ from django.template import Template, Context
+ context = context or {}
+ context = Context(context)
+ return Template(template).render(context)
+
+ template=(
+ r'{% load mail %}'
+ # TODO: Vyzkoušet i víc adresátů. (Nepamatuji si z hlavy syntaxi…)
+ r'{% maillink "Text" to="alice@test.test" subject="Oprava řešení" %}'
+ )
+ self.assertEquals(
+ render_template(template),
+ r'Text',
+ )
--
2.39.5
From ff996c2924ee8d0612a5326f4c0b03763763cf97 Mon Sep 17 00:00:00 2001
From: "Pavel \"LEdoian\" Turinsky"
Date: Sun, 8 Jan 2023 08:51:01 +0100
Subject: [PATCH 2/7] =?UTF-8?q?P=C5=99ejmenov=C3=A1n=C3=AD=20hodnocen?=
=?UTF-8?q?=C3=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Označení bylo zavádějící, protože se vůbec nejedná o objekt Hodnocení.
Neříkám, že nové jméno je nějak úchvatné, ale aspoň mě nemate a na
proměnnou s životností dva řádky je to stejně jedno…
---
odevzdavatko/views.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py
index 0100ef24..e983860a 100644
--- a/odevzdavatko/views.py
+++ b/odevzdavatko/views.py
@@ -235,8 +235,8 @@ class DetailReseniView(DetailView):
def get_context_data(self, **kw):
self.check_access()
ctx = super().get_context_data(**kw)
- hodnoceni = self.aktualni_hodnoceni()
- ctx["hodnoceni"] = hodnoceni
+ detaily_hodnoceni = self.aktualni_hodnoceni()
+ ctx["hodnoceni"] = detaily_hodnoceni
return ctx
def get(self, request, *args, **kwargs):
--
2.39.5
From efe1b4bb5a47de3d749f1bdfc7cedac9f2150a66 Mon Sep 17 00:00:00 2001
From: "Pavel \"LEdoian\" Turinsky"
Date: Sun, 8 Jan 2023 08:52:01 +0100
Subject: [PATCH 3/7] =?UTF-8?q?Pou=C5=BEit=C3=AD=20{%maillink%}=20v=20deta?=
=?UTF-8?q?ilu=20=C5=99e=C5=A1en=C3=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Ten řádek má sice pořád 117 znaků, ale IMHO je to o dost lepší.
Mně to i správně vyplňuje subjecty v Thunderbirdu, takže můj kód asi
není úplně mimo :-)
---
odevzdavatko/templates/odevzdavatko/detail.html | 6 +++++-
odevzdavatko/views.py | 3 +++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/odevzdavatko/templates/odevzdavatko/detail.html b/odevzdavatko/templates/odevzdavatko/detail.html
index 06f69609..379bdc68 100644
--- a/odevzdavatko/templates/odevzdavatko/detail.html
+++ b/odevzdavatko/templates/odevzdavatko/detail.html
@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% load static %}
{% load deadliny %}
+{% load mail %}
{% block content %}
@@ -14,7 +15,10 @@
{% if edit %}
Řešitelé:
- {% for r in object.resitele.all %}{{ r }} ({{ r.osoba.email }}){% if forloop.revcounter0 != 0 %}, {% endif %}{% endfor %}
+ {% for r in object.resitele.all %}
+ {{ r }}
+ ({% maillink r.osoba.email to=r.osoba.email subject=mailsubject %}){% if forloop.revcounter0 != 0 %}, {% endif %}
+ {% endfor %}
{% else %}
Řešitelé: {{ object.resitele.all | join:", " }}
diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py
index e983860a..3100eb9c 100644
--- a/odevzdavatko/views.py
+++ b/odevzdavatko/views.py
@@ -237,6 +237,9 @@ class DetailReseniView(DetailView):
ctx = super().get_context_data(**kw)
detaily_hodnoceni = self.aktualni_hodnoceni()
ctx["hodnoceni"] = detaily_hodnoceni
+
+ # Subject případného mailu (template neumí použitelně spojovat řetězce: https://stackoverflow.com/q/4386168)
+ ctx["mailsubject"] = "Oprava řešení M&M "+self.reseni.problem.first().hlavni_problem.nazev
return ctx
def get(self, request, *args, **kwargs):
--
2.39.5
From 0956b0780aa56a028c8bc6a3b295436aab28ddda Mon Sep 17 00:00:00 2001
From: "Pavel \"LEdoian\" Turinsky"
Date: Mon, 6 Feb 2023 20:13:29 +0100
Subject: [PATCH 4/7] =?UTF-8?q?Odd=C4=9Blen=C3=AD=20tagu=20{%mailurl%}?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
various/templatetags/mail.py | 7 ++++++-
various/tests.py | 20 ++++++++++++++++++--
2 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/various/templatetags/mail.py b/various/templatetags/mail.py
index fe11d218..972040f6 100644
--- a/various/templatetags/mail.py
+++ b/various/templatetags/mail.py
@@ -4,7 +4,7 @@ from urllib.request import quote as urlencode
register = template.Library()
@register.simple_tag
-def maillink(text: str, subject=None, body=None, to=[], attrs=None):
+def mailurl(*, subject=None, body=None, to=[]):
"""TODO: Dokumentace"""
if isinstance(to, str):
to = [to]
@@ -24,6 +24,11 @@ def maillink(text: str, subject=None, body=None, to=[], attrs=None):
url = parts[0] + '?' + str.join('&', parts[1:])
else:
url = parts[0]
+ return url
+
+@register.simple_tag
+def maillink(text, subject=None, body=None, to=[], attrs=None):
+ url = mailurl(subject=subject, body=body, to=to)
if not attrs: attrs = ''
mezera = ' '*bool(attrs)
full_link = f'{text}'
diff --git a/various/tests.py b/various/tests.py
index 7884e618..0abf4e26 100644
--- a/various/tests.py
+++ b/various/tests.py
@@ -1,6 +1,6 @@
from django.test import TestCase
# TODO: Možná vyrobit separátní soubory v tests/… než mít všechny testy v jednom souboru?
-from various.templatetags.mail import maillink
+from various.templatetags.mail import maillink, mailurl
class MailTagsTest(TestCase):
"""Testuje template tagy ohledně mailů."""
@@ -24,6 +24,16 @@ class MailTagsTest(TestCase):
self.assertRaises(ValueError, lambda: maillink('Nemám příjemce'))
self.assertRaises(TypeError, lambda: maillink()) # Nemá text, takže to shodí python
+ def test_mailurl(self):
+ self.assertEquals(mailurl(to='some@body.test'), r'mailto:some@body.test')
+ self.assertEquals(mailurl(to=['some@body.test']), r'mailto:some@body.test')
+ self.assertEquals(mailurl(to=['alice@test.test', 'bob@jinde.test']), r'mailto:alice@test.test,bob@jinde.test')
+ self.assertEquals(
+ mailurl(to='some@body.test', body='Tělo', subject='Předmět'),
+ r'mailto:some@body.test?subject=P%C5%99edm%C4%9Bt&body=T%C4%9Blo',
+ )
+ self.assertRaises(ValueError, lambda: mailurl())
+
def test_render_in_template(self):
# Pomocná funkce: vykreslí template do stringu
# Ref: https://stackoverflow.com/a/1690879
@@ -33,7 +43,7 @@ class MailTagsTest(TestCase):
context = Context(context)
return Template(template).render(context)
- template=(
+ template = (
r'{% load mail %}'
# TODO: Vyzkoušet i víc adresátů. (Nepamatuji si z hlavy syntaxi…)
r'{% maillink "Text" to="alice@test.test" subject="Oprava řešení" %}'
@@ -42,3 +52,9 @@ class MailTagsTest(TestCase):
render_template(template),
r'Text',
)
+
+ mailurltemplate = (
+ r'{% load mail %}'
+ r'{% mailurl to="alice@test.test" subject="Čau Alice" %}'
+ )
+ self.assertEquals(render_template(mailurltemplate), r'mailto:alice@test.test?subject=%C4%8Cau%20Alice')
--
2.39.5
From 65cd15ecbbc972e3b6d5642c1c3404396fe6617c Mon Sep 17 00:00:00 2001
From: "Pavel \"LEdoian\" Turinsky"
Date: Mon, 6 Feb 2023 20:27:19 +0100
Subject: [PATCH 5/7] =?UTF-8?q?Koment=C3=A1=C5=99=20k=20tomu,=20kde=20se?=
=?UTF-8?q?=20vyr=C3=A1b=C3=AD=20mailsubject?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
odevzdavatko/templates/odevzdavatko/detail.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/odevzdavatko/templates/odevzdavatko/detail.html b/odevzdavatko/templates/odevzdavatko/detail.html
index 379bdc68..74352509 100644
--- a/odevzdavatko/templates/odevzdavatko/detail.html
+++ b/odevzdavatko/templates/odevzdavatko/detail.html
@@ -17,6 +17,7 @@
Řešitelé:
{% for r in object.resitele.all %}
{{ r }}
+ {# DjangoTemplates neumí spojovat řetězce (https://stackoverflow.com/q/4386168), tak si necháváme vyrobit subject mailu ve view. #}
({% maillink r.osoba.email to=r.osoba.email subject=mailsubject %}){% if forloop.revcounter0 != 0 %}, {% endif %}
{% endfor %}
--
2.39.5
From f6cb669277d2143338b67f64475e804a85f9378b Mon Sep 17 00:00:00 2001
From: "Pavel \"LEdoian\" Turinsky"
Date: Mon, 6 Feb 2023 20:27:35 +0100
Subject: [PATCH 6/7] =?UTF-8?q?P=C5=99ejmenov=C3=A1n=C3=AD=20mailsubjectu?=
=?UTF-8?q?=20do=20=C4=8De=C5=A1tiny?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
odevzdavatko/templates/odevzdavatko/detail.html | 2 +-
odevzdavatko/views.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/odevzdavatko/templates/odevzdavatko/detail.html b/odevzdavatko/templates/odevzdavatko/detail.html
index 74352509..468b0322 100644
--- a/odevzdavatko/templates/odevzdavatko/detail.html
+++ b/odevzdavatko/templates/odevzdavatko/detail.html
@@ -18,7 +18,7 @@
{% for r in object.resitele.all %}
{{ r }}
{# DjangoTemplates neumí spojovat řetězce (https://stackoverflow.com/q/4386168), tak si necháváme vyrobit subject mailu ve view. #}
- ({% maillink r.osoba.email to=r.osoba.email subject=mailsubject %}){% if forloop.revcounter0 != 0 %}, {% endif %}
+ ({% maillink r.osoba.email to=r.osoba.email subject=predmetmailu %}){% if forloop.revcounter0 != 0 %}, {% endif %}
{% endfor %}
{% else %}
diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py
index 3100eb9c..57822bf4 100644
--- a/odevzdavatko/views.py
+++ b/odevzdavatko/views.py
@@ -239,7 +239,7 @@ class DetailReseniView(DetailView):
ctx["hodnoceni"] = detaily_hodnoceni
# Subject případného mailu (template neumí použitelně spojovat řetězce: https://stackoverflow.com/q/4386168)
- ctx["mailsubject"] = "Oprava řešení M&M "+self.reseni.problem.first().hlavni_problem.nazev
+ ctx["predmetmailu"] = "Oprava řešení M&M "+self.reseni.problem.first().hlavni_problem.nazev
return ctx
def get(self, request, *args, **kwargs):
--
2.39.5
From 04c3c6257cfee4dd9723a47db425d4a3ef237369 Mon Sep 17 00:00:00 2001
From: "Pavel \"LEdoian\" Turinsky"
Date: Mon, 6 Feb 2023 21:56:06 +0100
Subject: [PATCH 7/7] =?UTF-8?q?Podpora=20cc=20a=20bcc=20v=20{%maillink%}?=
=?UTF-8?q?=20[neotestov=C3=A1no]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../templates/odevzdavatko/detail.html | 3 +++
odevzdavatko/views.py | 1 +
various/templatetags/mail.py | 23 +++++++++++++++----
3 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/odevzdavatko/templates/odevzdavatko/detail.html b/odevzdavatko/templates/odevzdavatko/detail.html
index 468b0322..73265563 100644
--- a/odevzdavatko/templates/odevzdavatko/detail.html
+++ b/odevzdavatko/templates/odevzdavatko/detail.html
@@ -21,6 +21,9 @@
({% maillink r.osoba.email to=r.osoba.email subject=predmetmailu %}){% if forloop.revcounter0 != 0 %}, {% endif %}
{% endfor %}
+
+ {% maillink "Poslat mail všem řešitelům" bcc=maily_vsech_resitelu subject=predmetmailu %}
+
{% else %}
Řešitelé: {{ object.resitele.all | join:", " }}
{% endif %}
diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py
index 57822bf4..9ac1ac29 100644
--- a/odevzdavatko/views.py
+++ b/odevzdavatko/views.py
@@ -240,6 +240,7 @@ class DetailReseniView(DetailView):
# Subject případného mailu (template neumí použitelně spojovat řetězce: https://stackoverflow.com/q/4386168)
ctx["predmetmailu"] = "Oprava řešení M&M "+self.reseni.problem.first().hlavni_problem.nazev
+ ctx["maily_vsech_resitelu"] = [y for x in self.reseni.resitele.all().values_list('osoba__email') for y in x]
return ctx
def get(self, request, *args, **kwargs):
diff --git a/various/templatetags/mail.py b/various/templatetags/mail.py
index 972040f6..ecbb2a39 100644
--- a/various/templatetags/mail.py
+++ b/various/templatetags/mail.py
@@ -4,21 +4,34 @@ from urllib.request import quote as urlencode
register = template.Library()
@register.simple_tag
-def mailurl(*, subject=None, body=None, to=[]):
- """TODO: Dokumentace"""
+def mailurl(*, subject=None, body=None, to=[], cc=[], bcc=[]):
+ """Tag na vytváření správně zakódované mailto: adresy
+
+ Ref: RFC 6068, """
if isinstance(to, str):
to = [to]
+ if isinstance(cc, str):
+ cc = [cc]
+ if isinstance(bcc, str):
+ bcc = [bcc]
assert isinstance(to, list)
+ assert isinstance(cc, list)
+ assert isinstance(bcc, list)
+ # FIXME: adresa není správně zakódovaná, rozbije se to na adresách s divnými znaky
parts = [
f'mailto:{str.join(",", to)}',
]
- if len(to) < 1:
+ if len(to) + len(cc) + len(bcc) < 1:
raise ValueError('Cannot mail to empty set of people')
if subject:
parts.append(f'subject={urlencode(subject)}')
if body:
parts.append(f'body={urlencode(body)}')
+ if len(cc) > 0:
+ parts.append(f'cc={str.join(",", cc)}')
+ if len(bcc) > 0:
+ parts.append(f'bcc={str.join(",", bcc)}')
if len(parts) > 1:
url = parts[0] + '?' + str.join('&', parts[1:])
@@ -27,8 +40,8 @@ def mailurl(*, subject=None, body=None, to=[]):
return url
@register.simple_tag
-def maillink(text, subject=None, body=None, to=[], attrs=None):
- url = mailurl(subject=subject, body=body, to=to)
+def maillink(text, subject=None, body=None, to=[], cc=[], bcc=[], attrs=None):
+ url = mailurl(subject=subject, body=body, to=to, cc=cc, bcc=bcc)
if not attrs: attrs = ''
mezera = ' '*bool(attrs)
full_link = f'{text}'
--
2.39.5