-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1582 from freedomofpress/add-conversation-digest
Add conversation.Transcript
- Loading branch information
Showing
19 changed files
with
289 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Extract from Python source files and Jinja templates. | ||
|
||
[python: **.py] | ||
|
||
[jinja2: **.jinja] | ||
# Warn on Jinja syntax errors | ||
# (https://jinja.palletsprojects.com/en/3.0.x/integration/#babel). | ||
silent=False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .transcript import Transcript # noqa: F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .transcript import Transcript # noqa: F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .factory import transcribe # noqa: F401 | ||
from .item import Item # noqa: F401 |
16 changes: 16 additions & 0 deletions
16
securedrop_client/conversation/transcript/items/factory.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from typing import Optional | ||
|
||
from securedrop_client import db as database | ||
|
||
from .file import File | ||
from .item import Item | ||
from .message import Message | ||
|
||
|
||
def transcribe(record: database.Base) -> Optional[Item]: | ||
if isinstance(record, database.Message) or isinstance(record, database.Reply): | ||
return Message(record) | ||
if isinstance(record, database.File): | ||
return File(record) | ||
else: | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from securedrop_client import db as database | ||
|
||
from .item import Item | ||
|
||
|
||
class File(Item): | ||
type = "file" | ||
|
||
def __init__(self, record: database.File): | ||
super().__init__() | ||
|
||
self.filename = record.filename | ||
self.sender = record.source.journalist_designation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from typing import Optional | ||
|
||
|
||
class Item: | ||
""" | ||
A transcript item. | ||
Transcript items must define their `type` to be rendered by the transcript template. | ||
""" | ||
|
||
type: Optional[str] = None |
19 changes: 19 additions & 0 deletions
19
securedrop_client/conversation/transcript/items/message.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from typing import Union | ||
|
||
from securedrop_client import db as database | ||
|
||
from .item import Item | ||
|
||
|
||
class Message(Item): | ||
type = "message" | ||
|
||
def __init__(self, record: Union[database.Message, database.Reply]): | ||
super().__init__() | ||
|
||
self.content = record.content | ||
|
||
if isinstance(record, database.Message): | ||
self.sender = record.source.journalist_designation | ||
else: | ||
self.sender = record.journalist.username |
16 changes: 16 additions & 0 deletions
16
securedrop_client/conversation/transcript/templates/transcript.txt.jinja
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{% if items|length <= 0 %}{% trans %}No messages.{% endtrans %}{% else %} | ||
{% for item in items %} | ||
{% if item.type == "message" %} | ||
{% if loop.changed(item.sender) %} | ||
{% trans sender=item.sender %}{{ sender }} wrote:{% endtrans +%} | ||
{% endif %} | ||
{{ item.content }} | ||
{% elif item.type == "file" %} | ||
{% trans sender=item.sender %}{{ sender }} sent:{% endtrans +%} | ||
{% trans filename=item.filename %}File: {{ filename }}{% endtrans +%} | ||
{% endif %} | ||
{% if not loop.last %} | ||
------ | ||
{% endif %} | ||
{% endfor %} | ||
{% endif %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import gettext | ||
from typing import Optional | ||
|
||
from jinja2 import Environment, PackageLoader, select_autoescape | ||
|
||
from securedrop_client import db as database | ||
|
||
from .items import Item | ||
from .items import transcribe as transcribe_item | ||
|
||
env = Environment( | ||
loader=PackageLoader("securedrop_client.conversation.transcript"), | ||
autoescape=select_autoescape(), | ||
extensions=["jinja2.ext.i18n"], | ||
# Since our plain-text templates have literal whitespace: | ||
lstrip_blocks=True, | ||
trim_blocks=True, | ||
) | ||
env.install_gettext_translations(gettext) # type: ignore [attr-defined] | ||
|
||
|
||
def transcribe(record: database.Base) -> Optional[Item]: | ||
return transcribe_item(record) | ||
|
||
|
||
class Transcript: | ||
def __init__(self, conversation: database.Source) -> None: | ||
self._items = list( | ||
filter( | ||
lambda record: record is not None and record.type is not None, | ||
[transcribe(record) for record in conversation.collection], | ||
) | ||
) | ||
self._template = env.get_template("transcript.txt.jinja") | ||
|
||
def __str__(self) -> str: | ||
return self._template.render(items=self._items) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import unittest | ||
from datetime import datetime | ||
from textwrap import dedent | ||
|
||
from securedrop_client import conversation | ||
from securedrop_client import db as database | ||
|
||
|
||
class TestConversationTranscript(unittest.TestCase): | ||
def setUp(self): | ||
|
||
source = database.Source( | ||
journalist_designation="happy-bird", | ||
) | ||
files = [ | ||
database.File( | ||
filename="4-memo.pdf.gpg", | ||
is_downloaded=True, | ||
), | ||
database.File( | ||
filename="9-memo.zip.gpg", | ||
is_downloaded=True, | ||
), | ||
] | ||
messages = [ | ||
database.Message( | ||
filename="1-message.gpg", | ||
is_downloaded=True, | ||
content="Hello! I think this is newsworthy: ...", | ||
), | ||
database.Message( | ||
filename="6-message.gpg", | ||
is_downloaded=True, | ||
content="I can send you more if you're interested.", | ||
), | ||
database.Message( | ||
filename="5-message.gpg", | ||
is_downloaded=True, | ||
content="Here is a document with details!", | ||
), | ||
database.Message( | ||
filename="8-message.gpg", | ||
is_downloaded=True, | ||
content="Sure.", | ||
), | ||
] | ||
interested_journalist = database.User(username="interested-journalist") | ||
other_journalist = database.User(username="other-journalist") | ||
replies = [ | ||
database.Reply( | ||
journalist=interested_journalist, | ||
filename="2-reply.gpg", | ||
is_downloaded=True, | ||
content=dedent( | ||
"""\ | ||
Thank you for the tip! | ||
Can you tell me more about... ? | ||
""" | ||
), | ||
), | ||
database.Reply( | ||
journalist=interested_journalist, | ||
filename="3-reply.gpg", | ||
is_downloaded=True, | ||
content=dedent( | ||
"""\ | ||
Do you have proof of...? | ||
""" | ||
), | ||
), | ||
database.Reply( | ||
journalist=other_journalist, | ||
filename="7-reply.gpg", | ||
is_downloaded=True, | ||
content=dedent("Yes, the document you sent was useful, I'd love to see more."), | ||
), | ||
] | ||
draft_reply = database.DraftReply( | ||
content="Let me think...", | ||
file_counter=2, | ||
timestamp=datetime.now(), | ||
) | ||
source.files = files | ||
source.messages = messages | ||
source.replies = replies | ||
source.draftreplies = [draft_reply] | ||
|
||
self._source = source | ||
|
||
def test_indicates_explicitly_absence_of_messages(self): | ||
source = database.Source() | ||
assert str(conversation.Transcript(source)) == "No messages." | ||
|
||
def test_renders_all_messages(self): | ||
assert str(conversation.Transcript(self._source)) == dedent( | ||
"""\ | ||
happy-bird wrote: | ||
Hello! I think this is newsworthy: ... | ||
------ | ||
interested-journalist wrote: | ||
Thank you for the tip! | ||
Can you tell me more about... ? | ||
------ | ||
Do you have proof of...? | ||
------ | ||
happy-bird sent: | ||
File: 4-memo.pdf.gpg | ||
------ | ||
happy-bird wrote: | ||
Here is a document with details! | ||
------ | ||
I can send you more if you're interested. | ||
------ | ||
other-journalist wrote: | ||
Yes, the document you sent was useful, I'd love to see more. | ||
------ | ||
happy-bird wrote: | ||
Sure. | ||
------ | ||
happy-bird sent: | ||
File: 9-memo.zip.gpg | ||
""" | ||
) |