# What this PR does Remove [`apps.get_model`](https://docs.djangoproject.com/en/3.2/ref/applications/#django.apps.apps.get_model) invocations and use inline `import` statements in places where models are imported within functions/methods to avoid circular imports. I believe `import` statements are more appropriate for most use cases as they allow for better static code analysis & formatting, and solve the issue of circular imports without being unnecessarily dynamic as `apps.get_model`. With `import` statements, it's possible to: - Jump to model definitions in most IDEs - Automatically sort inline imports with `isort` - Find import errors faster/easier (most IDEs highlight broken imports) - Have more consistency across regular & inline imports when importing models This PR also adds a flake8 rule to ban imports of `django.apps.apps`, so it's harder to use `apps.get_model` by mistake (it's possible to ignore this rule by using `# noqa: I251`). The rule is not enforced on directories with migration files, because `apps.get_model` is often used to get a historical state of a model, which is useful when writing migrations ([see this SO answer for more details](https://stackoverflow.com/a/37769213)). So `apps.get_model` is considered OK in migrations (even necessary in some cases). ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required)
98 lines
4 KiB
Python
98 lines
4 KiB
Python
import re
|
|
|
|
import emoji
|
|
from slackviewer.formatter import SlackFormatter as SlackFormatterBase
|
|
|
|
|
|
class SlackFormatter(SlackFormatterBase):
|
|
_LINK_PAT = re.compile(r"<(https|http|mailto):[A-Za-z0-9_\.\-\/\?\,\=\#\:\@\& ]+\|[^>]+>")
|
|
|
|
def __init__(self, organization):
|
|
self.__ORGANIZATION = organization
|
|
self.channel_mention_format = "#{}"
|
|
self.user_mention_format = "@{}"
|
|
self.hyperlink_mention_format = '<a href="{url}">{title}</a>'
|
|
|
|
def find_user(self, message):
|
|
raise NotImplementedError()
|
|
|
|
def format(self, message):
|
|
"""
|
|
Overriden original render_text method.
|
|
Now it is responsible only for formatting slack mentions, channel names, etc.
|
|
"""
|
|
if message is None:
|
|
return
|
|
message = message.replace("<!channel>", "@channel")
|
|
message = message.replace("<!channel|@channel>", "@channel")
|
|
message = message.replace("<!here>", "@here")
|
|
message = message.replace("<!here|@here>", "@here")
|
|
message = message.replace("<!everyone>", "@everyone")
|
|
message = message.replace("<!everyone|@everyone>", "@everyone")
|
|
message = self.slack_to_accepted_emoji(message)
|
|
|
|
# Handle mentions of users, channels and bots (e.g "<@U0BM1CGQY|calvinchanubc> has joined the channel")
|
|
message = self._MENTION_PAT.sub(self._sub_annotated_mention, message)
|
|
# Handle links
|
|
message = self._LINK_PAT.sub(self._sub_hyperlink, message)
|
|
# Introduce unicode emoji
|
|
message = emoji.emojize(message, language="alias")
|
|
|
|
return message
|
|
|
|
def _sub_hyperlink(self, matchobj):
|
|
compound = matchobj.group(0)[1:-1]
|
|
if len(compound.split("|")) == 2:
|
|
url, title = compound.split("|")
|
|
else:
|
|
url, title = compound, compound
|
|
result = self.hyperlink_mention_format.format(url=url, title=title)
|
|
return result
|
|
|
|
def _sub_annotated_mention(self, matchobj):
|
|
"""
|
|
Overrided method to use db search instead of self.__USER_DATA and self.__CHANNEL_DATA (see original method)
|
|
to search channels and users by their slack_ids
|
|
"""
|
|
# Matchobj have format <channel_id/channel_name> or <user_id/user_name>
|
|
ref_id = matchobj.group(1)[1:] # drop #/@ from the start, we don't care.
|
|
annotation = matchobj.group(2)
|
|
# check if mention channel
|
|
if ref_id.startswith("C"):
|
|
mention_format = self.channel_mention_format
|
|
# channel could be mentioned only with its slack_id <channel_id>
|
|
if not annotation:
|
|
# search channel_name by slack_id in cache
|
|
annotation = self._sub_annotated_mention_slack_channel(ref_id)
|
|
else: # Same for user
|
|
mention_format = self.user_mention_format
|
|
if not annotation:
|
|
annotation = self._sub_annotated_mention_slack_user(ref_id)
|
|
return mention_format.format(annotation)
|
|
|
|
def _sub_annotated_mention_slack_channel(self, ref_id):
|
|
channel = None
|
|
slack_team_identity = self.__ORGANIZATION.slack_team_identity
|
|
if slack_team_identity is not None:
|
|
cached_channels = slack_team_identity.get_cached_channels(slack_id=ref_id)
|
|
if len(cached_channels) > 0:
|
|
channel = cached_channels[0].name
|
|
annotation = channel if channel else ref_id
|
|
else:
|
|
annotation = ref_id
|
|
return annotation
|
|
|
|
def _sub_annotated_mention_slack_user(self, ref_id):
|
|
from apps.slack.models import SlackUserIdentity
|
|
|
|
slack_user_identity = SlackUserIdentity.objects.filter(
|
|
slack_team_identity=self.__ORGANIZATION.slack_team_identity, slack_id=ref_id
|
|
).first()
|
|
|
|
annotation = ref_id
|
|
if slack_user_identity is not None:
|
|
if slack_user_identity.profile_display_name:
|
|
annotation = slack_user_identity.profile_display_name
|
|
elif slack_user_identity.slack_verbal:
|
|
annotation = slack_user_identity.slack_verbal
|
|
return annotation
|