Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add URN schemes to variable completions #1067

Merged
merged 18 commits into from Feb 1, 2017
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions temba/contacts/models.py
Expand Up @@ -1736,10 +1736,14 @@ def get_urn_display(self, org=None, scheme=None, formatted=True, international=F
if not org:
org = self.org

urn = self.get_urn(scheme)

if not urn:
return ''

if org.is_anon:
return self.anon_identifier
return ContactURN.ANON_MASK

urn = self.get_urn(scheme)
return urn.get_display(org=org, formatted=formatted, international=international) if urn else ''

def raw_tel(self):
Expand Down
24 changes: 14 additions & 10 deletions temba/contacts/templatetags/contacts.py
Expand Up @@ -3,7 +3,7 @@
from django import template
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from temba.contacts.models import Contact, ContactURN, EMAIL_SCHEME, EXTERNAL_SCHEME, FACEBOOK_SCHEME
from temba.contacts.models import ContactURN, EMAIL_SCHEME, EXTERNAL_SCHEME, FACEBOOK_SCHEME
from temba.contacts.models import TELEGRAM_SCHEME, TEL_SCHEME, TWITTER_SCHEME, TWILIO_SCHEME

register = template.Library()
Expand Down Expand Up @@ -60,15 +60,19 @@ def name_or_urn(contact, org):


@register.filter
def format_urn(urn_or_contact, org):
if isinstance(urn_or_contact, ContactURN):
urn_val = urn_or_contact.get_display(org=org, international=True)
return urn_val if urn_val != ContactURN.ANON_MASK else '\u2022' * 8 # replace *'s with prettier HTML entity
elif isinstance(urn_or_contact, Contact):
# will render contact's highest priority URN
return urn_or_contact.get_urn_display(org=org, international=True)
else: # pragma: no cover
raise ValueError('Must be a URN or contact')
def format_urn(urn, org):
urn_val = urn.get_display(org=org, international=True)
if urn_val == ContactURN.ANON_MASK:
return '\u2022' * 8 # replace *'s with prettier HTML entity
return urn_val


@register.filter
def format_contact(contact, org):
display = contact.get_display(org=org)
if display == ContactURN.ANON_MASK:
return '\u2022' * 8 # replace *'s with prettier HTML entity
return display


@register.filter
Expand Down
13 changes: 7 additions & 6 deletions temba/contacts/tests.py
Expand Up @@ -873,11 +873,12 @@ def test_contact_display(self):
self.assertEqual("Wolfeschlegelstei...", mr_long_name.get_display(short=True))
self.assertEqual("Billy Nophone", self.billy.get_display())

self.assertEqual(self.joe.anon_identifier, self.joe.get_urn_display(org=self.org, formatted=False))
self.assertEqual(self.joe.anon_identifier, self.joe.get_urn_display())
self.assertEqual(self.voldemort.anon_identifier, self.voldemort.get_urn_display())
self.assertEqual(mr_long_name.anon_identifier, mr_long_name.get_urn_display())
self.assertEqual(self.billy.anon_identifier, self.billy.get_urn_display())
self.assertEqual(ContactURN.ANON_MASK, self.joe.get_urn_display(org=self.org, formatted=False))
self.assertEqual(ContactURN.ANON_MASK, self.joe.get_urn_display())
self.assertEqual(ContactURN.ANON_MASK, self.voldemort.get_urn_display())
self.assertEqual(ContactURN.ANON_MASK, mr_long_name.get_urn_display())
self.assertEqual('', self.billy.get_urn_display())
self.assertEqual('', self.billy.get_urn_display(scheme=TEL_SCHEME))

self.assertEqual("Joe Blow", six.text_type(self.joe))
self.assertEqual("%010d" % self.voldemort.pk, six.text_type(self.voldemort))
Expand Down Expand Up @@ -1247,7 +1248,7 @@ def test_history(self):
contact_urn=self.joe.urns.all().first())

# fetch our contact history
with self.assertNumQueries(64):
with self.assertNumQueries(65):
response = self.fetch_protected(url, self.admin)

# activity should include all messages in the last 90 days, the channel event, the call, and the flow run
Expand Down
8 changes: 8 additions & 0 deletions temba/flows/tests.py
Expand Up @@ -4109,6 +4109,14 @@ def assert_in_response(response, data_key, key):
assert_in_response(response, 'function_completions', 'ABS')
assert_in_response(response, 'function_completions', 'YEAR')

# a Twitter channel
Channel.create(self.org, self.user, None, 'TT')

response = self.client.get('%s?flow=%d' % (reverse('flows.flow_completion'), flow.pk))
response = response.json()

assert_in_response(response, 'message_completions', 'contact.twitter')

def test_bulk_exit(self):
flow = self.get_flow('favorites')
color = RuleSet.objects.get(label='Color', flow=flow)
Expand Down
11 changes: 9 additions & 2 deletions temba/flows/views.py
Expand Up @@ -26,8 +26,9 @@
from itertools import chain
from smartmin.views import SmartCRUDL, SmartCreateView, SmartReadView, SmartListView, SmartUpdateView
from smartmin.views import SmartDeleteView, SmartTemplateView, SmartFormView
from temba.channels.models import Channel
from temba.contacts.fields import OmniboxField
from temba.contacts.models import Contact, ContactGroup, ContactField, TEL_SCHEME
from temba.contacts.models import Contact, ContactGroup, ContactField, TEL_SCHEME, ContactURN
from temba.ivr.models import IVRCall
from temba.ussd.models import USSDSession
from temba.orgs.views import OrgPermsMixin, OrgObjPermsMixin, ModalMixin
Expand Down Expand Up @@ -789,7 +790,13 @@ def render_to_response(self, context, **response_kwargs):
dict(name='contact.uuid', display=six.text_type(_("Contact UUID"))),
dict(name='new_contact', display=six.text_type(_('New Contact')))
]
contact_variables += [dict(name="contact.%s" % field.key, display=field.label) for field in ContactField.objects.filter(org=org, is_active=True)]

contact_variables += [dict(name="contact.%s" % scheme, display=six.text_type(_("Contact %s" % label)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this will read "Contact Facebook" or "Contact Twitter" ya? Seems we might want something more like "Contact Facebook URN" and "Contact Twitter URN" instead.. Though from a naming perspective that is slightly problematic since these won't be full URNs but just the paths right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we'll get "Contact Facebook identifier" or "Contact Twitter handle"

as seen on
https://github.com/nyaruka/rapidpro/blob/master/temba/contacts/models.py#L61

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ok great.

for scheme, label in ContactURN.SCHEME_CHOICES if scheme != TEL_SCHEME and scheme in
org.get_schemes(Channel.ROLE_SEND)]

contact_variables += [dict(name="contact.%s" % field.key, display=field.label) for field in
ContactField.objects.filter(org=org, is_active=True)]

date_variables = [
dict(name='date', display=six.text_type(_('Current Date and Time'))),
Expand Down
8 changes: 8 additions & 0 deletions temba/msgs/tests.py
Expand Up @@ -210,6 +210,14 @@ def test_send_message_auto_completion_processor(self):
# contact fields are included at the end in alphabetical order
self.assertEquals(response.context['completions'], json.dumps(completions))

# a Twitter channel
Channel.create(self.org, self.user, None, 'TT')
completions.insert(-2, dict(name="contact.%s" % 'twitter', display="Contact %s" % "Twitter handle"))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the substitution here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completions is a list and I want to keep the order right so I inserted before the 2 dict representing the contact fields above

response = self.client.get(outbox_url)
# the Twitter URN scheme is included
self.assertEquals(response.context['completions'], json.dumps(completions))

def test_create_outgoing(self):
tel_urn = "tel:250788382382"
tel_contact = Contact.get_or_create(self.org, self.user, urns=[tel_urn])
Expand Down
6 changes: 5 additions & 1 deletion temba/msgs/views.py
Expand Up @@ -19,7 +19,7 @@
from smartmin.views import SmartCreateView, SmartCRUDL, SmartDeleteView, SmartFormView, SmartListView, SmartReadView, SmartUpdateView
from temba.channels.models import Channel
from temba.contacts.fields import OmniboxField
from temba.contacts.models import ContactGroup, URN
from temba.contacts.models import ContactGroup, URN, ContactURN, TEL_SCHEME
from temba.formax import FormaxMixin
from temba.orgs.views import OrgPermsMixin, OrgObjPermsMixin, ModalMixin
from temba.utils import analytics, on_transaction_commit
Expand Down Expand Up @@ -55,6 +55,10 @@ def send_message_auto_complete_processor(request):
completions.append(dict(name="date.tomorrow", display=six.text_type(_("Tomorrow's Date"))))
completions.append(dict(name="date.yesterday", display=six.text_type(_("Yesterday's Date"))))

for scheme, label in ContactURN.SCHEME_CHOICES:
if scheme != TEL_SCHEME and scheme in org.get_schemes(Channel.ROLE_SEND):
completions.append(dict(name="contact.%s" % scheme, display=six.text_type(_("Contact %s" % label))))

for field in org.contactfields.filter(is_active=True).order_by('label'):
display = six.text_type(_("Contact Field: %(label)s")) % {'label': field.label}
completions.append(dict(name="contact.%s" % str(field.key), display=display))
Expand Down
2 changes: 1 addition & 1 deletion temba/orgs/tests.py
Expand Up @@ -2418,7 +2418,7 @@ def test_subflow_dependencies(self):
response = self.client.get(reverse('orgs.org_export'))

from bs4 import BeautifulSoup
soup = BeautifulSoup(response.content)
soup = BeautifulSoup(response.content, "html.parser")
group = str(soup.findAll("div", {"class": "exportables bucket"})[0])

self.assertIn('Parent Flow', group)
Expand Down
2 changes: 1 addition & 1 deletion templates/contacts/contact_list.haml
Expand Up @@ -165,7 +165,7 @@

%td.value-phone.field_phone
%nobr
{{ object|format_urn:user_org }}
{{ object|format_contact:user_org }}

-for field in contact_fields
-if field.show_in_table
Expand Down
2 changes: 1 addition & 1 deletion templates/contacts/contact_read.haml
Expand Up @@ -197,7 +197,7 @@
-if contact.name
{{contact.name}}
-else
{{contact|format_urn:user_org}}
{{contact|format_contact:user_org}}
%p
-trans "Once they are deleted, they will be gone forever. There is no way to undo this operation."

Expand Down
2 changes: 1 addition & 1 deletion templates/flows/flow_run_table.haml
Expand Up @@ -12,7 +12,7 @@
-if run.contact.name
{{run.contact.name|truncatechars:100}}
-else
{{ run.contact|format_urn:user_org }}
{{ run.contact|format_contact:user_org }}

-for value in run.value_list
%td
Expand Down
2 changes: 1 addition & 1 deletion templates/flows/flow_runs_partial.haml
Expand Up @@ -12,7 +12,7 @@
-if run.contact.name
{{run.contact.name|truncatechars:100}}
-else
{{ run.contact|format_urn:user_org }}
{{ run.contact|format_contact:user_org }}

-for value in run.value_list
%td
Expand Down