def test_labels(self): url = reverse('api.v2.labels') self.assertEndpointAccess(url) important = Label.get_or_create(self.org, self.admin, "Important") feedback = Label.get_or_create(self.org, self.admin, "Feedback") Label.get_or_create(self.org2, self.admin2, "Spam") msg = self.create_msg(direction="I", text="Hello", contact=self.frank) important.toggle_label([msg], add=True) # no filtering with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 1): response = self.fetchJSON(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertEqual(response.json['results'], [ {'uuid': feedback.uuid, 'name': "Feedback", 'count': 0}, {'uuid': important.uuid, 'name': "Important", 'count': 1} ]) # filter by UUID response = self.fetchJSON(url, 'uuid=%s' % feedback.uuid) self.assertEqual(response.json['results'], [{'uuid': feedback.uuid, 'name': "Feedback", 'count': 0}])
def from_json(cls, org, json_obj): from temba.msgs.models import Label labels_data = json_obj.get(cls.LABELS) labels = [] for label_data in labels_data: if isinstance(label_data, dict): label_uuid = label_data.get("uuid", None) label_name = label_data.get("name") if label_uuid and Label.label_objects.filter( org=org, uuid=label_uuid).first(): label = Label.label_objects.filter( org=org, uuid=label_uuid).first() if label: labels.append(label) else: # pragma: needs cover labels.append( Label.get_or_create(org, org.get_user(), label_name)) elif isinstance(label_data, str): if label_data and label_data[0] == "@": # label name is a variable substitution labels.append(label_data) else: # pragma: needs cover labels.append( Label.get_or_create(org, org.get_user(), label_data)) else: # pragma: needs cover raise ValueError("Label data must be a dict or string") return cls(json_obj.get(cls.UUID), labels)
def save(self): messages = self.validated_data['messages'] action = self.validated_data['action'] label = self.validated_data.get('label') label_name = self.validated_data.get('label_name') if action == self.LABEL: if not label: label = Label.get_or_create(self.context['org'], self.context['user'], label_name) label.toggle_label(messages, add=True) elif action == self.UNLABEL: if not label: label = Label.label_objects.filter(org=self.context['org'], is_active=True, name=label_name).first() if label: label.toggle_label(messages, add=False) else: for msg in messages: if action == self.ARCHIVE: msg.archive() elif action == self.RESTORE: msg.restore() elif action == self.DELETE: msg.release()
def test_add_label(self): label = Label.get_or_create(self.org, self.user, "green label") action = AddLabelAction(str(uuid4()), [label, "@step.contact"]) action = self._serialize_deserialize(action) self.assertEqual([label, "@step.contact"], action.labels)
def save(self): name = self.validated_data.get('name') if self.instance: self.instance.name = name self.instance.save(update_fields=('name',)) return self.instance else: return Label.get_or_create(self.context['org'], self.context['user'], name)
def save(self): name = self.validated_data.get("name") if self.instance: self.instance.name = name self.instance.save(update_fields=("name",)) return self.instance else: return Label.get_or_create(self.context["org"], self.context["user"], name)
def _create_labels(self, count, base_names, messages): """ Creates the given number of labels and fills them with messages """ labels = [] num_bases = len(base_names) for g in range(0, count): name = '%s %d' % (base_names[g % num_bases], g + 1) label = Label.create(self.org, self.user, name) labels.append(label) assign_to = messages[(g % num_bases)::num_bases] for msg in assign_to: Msg.objects.get(pk=msg.pk).labels.add(label) return labels
def save(self): action = self.validated_data["action"] label = self.validated_data.get("label") label_name = self.validated_data.get("label_name") requested_message_ids = self.initial_data["messages"] requested_messages = self.validated_data["messages"] # requested_messages contains nones where msg no longer exists so compile lists of real messages and missing ids messages = [] missing_message_ids = [] for m, msg in enumerate(requested_messages): if msg is not None: messages.append(msg) else: missing_message_ids.append(requested_message_ids[m]) if action == self.LABEL: if not label: label = Label.get_or_create(self.context["org"], self.context["user"], label_name) label.toggle_label(messages, add=True) elif action == self.UNLABEL: if not label: label = Label.label_objects.filter(org=self.context["org"], is_active=True, name=label_name).first() if label: label.toggle_label(messages, add=False) else: for msg in messages: if action == self.ARCHIVE: msg.archive() elif action == self.RESTORE: msg.restore() elif action == self.DELETE: msg.release() return BulkActionFailure( missing_message_ids) if missing_message_ids else None
def test_serialize_label(self): spam = Label.get_or_create(self.org, self.admin, "Spam") self.assertEqual(serialize_label(spam), {"uuid": str(spam.uuid), "name": "Spam"})
def test_messages(self): url = reverse('api.v2.messages') # make sure user rights are correct self.assertEndpointAccess(url, "folder=inbox") # make sure you have to pass in something to filter by response = self.fetchJSON(url) self.assertResponseError(response, None, "You must specify one of the contact, folder, label, broadcast, id parameters") # create some messages joe_msg1 = self.create_msg(direction='I', msg_type='F', text="Howdy", contact=self.joe) frank_msg1 = self.create_msg(direction='I', msg_type='I', text="Bonjour", contact=self.frank, channel=self.twitter) joe_msg2 = self.create_msg(direction='O', msg_type='I', text="How are you?", contact=self.joe, status='Q') frank_msg2 = self.create_msg(direction='O', msg_type='I', text="Ça va?", contact=self.frank, status='D') joe_msg3 = self.create_msg(direction='I', msg_type='F', text="Good", contact=self.joe) frank_msg3 = self.create_msg(direction='I', msg_type='I', text="Bien", contact=self.frank, channel=self.twitter, visibility='A') # add a surveyor message (no URN etc) joe_msg4 = self.create_msg(direction='O', msg_type='F', text="Surveys!", contact=self.joe, contact_urn=None, status='S', channel=None, sent_on=timezone.now()) # add a deleted message deleted_msg = self.create_msg(direction='I', msg_type='I', text="!@$!%", contact=self.frank, visibility='D') # add a test contact message self.create_msg(direction='I', msg_type='F', text="Hello", contact=self.test_contact) # add message in other org self.create_msg(direction='I', msg_type='I', text="Guten tag!", contact=self.hans, org=self.org2) # label some of the messages, this will change our modified on as well for our `incoming` view label = Label.get_or_create(self.org, self.admin, "Spam") # we do this in two calls so that we can predict ordering later label.toggle_label([frank_msg3], add=True) label.toggle_label([frank_msg1], add=True) label.toggle_label([joe_msg3], add=True) frank_msg1.refresh_from_db(fields=('modified_on',)) joe_msg3.refresh_from_db(fields=('modified_on',)) # filter by inbox with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 7): response = self.fetchJSON(url, 'folder=INBOX') self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertResultsById(response, [frank_msg1]) self.assertMsgEqual(response.json['results'][0], frank_msg1, msg_type='inbox', msg_status='queued', msg_visibility='visible') # filter by incoming, should get deleted messages too with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 7): response = self.fetchJSON(url, 'folder=incoming') self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertResultsById(response, [joe_msg3, frank_msg1, frank_msg3, deleted_msg, joe_msg1]) self.assertMsgEqual(response.json['results'][0], joe_msg3, msg_type='flow', msg_status='queued', msg_visibility='visible') # filter by folder (flow) response = self.fetchJSON(url, 'folder=flows') self.assertResultsById(response, [joe_msg3, joe_msg1]) # filter by folder (archived) response = self.fetchJSON(url, 'folder=archived') self.assertResultsById(response, [frank_msg3]) # filter by folder (outbox) response = self.fetchJSON(url, 'folder=outbox') self.assertResultsById(response, [joe_msg2]) # filter by folder (sent) response = self.fetchJSON(url, 'folder=sent') self.assertResultsById(response, [joe_msg4, frank_msg2]) # filter by invalid view response = self.fetchJSON(url, 'folder=invalid') self.assertResultsById(response, []) # filter by id response = self.fetchJSON(url, 'id=%d' % joe_msg3.pk) self.assertResultsById(response, [joe_msg3]) # filter by contact response = self.fetchJSON(url, 'contact=%s' % self.joe.uuid) self.assertResultsById(response, [joe_msg4, joe_msg3, joe_msg2, joe_msg1]) # filter by invalid contact response = self.fetchJSON(url, 'contact=invalid') self.assertResultsById(response, []) # filter by label name response = self.fetchJSON(url, 'label=Spam') self.assertResultsById(response, [joe_msg3, frank_msg1]) # filter by label UUID response = self.fetchJSON(url, 'label=%s' % label.uuid) self.assertResultsById(response, [joe_msg3, frank_msg1]) # filter by invalid label response = self.fetchJSON(url, 'label=invalid') self.assertResultsById(response, []) # filter by before (inclusive) response = self.fetchJSON(url, 'folder=incoming&before=%s' % format_datetime(frank_msg1.modified_on)) self.assertResultsById(response, [frank_msg1, frank_msg3, deleted_msg, joe_msg1]) # filter by after (inclusive) response = self.fetchJSON(url, 'folder=incoming&after=%s' % format_datetime(frank_msg1.modified_on)) self.assertResultsById(response, [joe_msg3, frank_msg1]) # filter by broadcast broadcast = Broadcast.create(self.org, self.user, "A beautiful broadcast", [self.joe, self.frank]) broadcast.send() response = self.fetchJSON(url, 'broadcast=%s' % broadcast.pk) expected = {m.pk for m in broadcast.msgs.all()} results = {m['id'] for m in response.json['results']} self.assertEqual(expected, results) # can't filter by more than one of contact, folder, label or broadcast together for query in ('contact=%s&label=Spam' % self.joe.uuid, 'label=Spam&folder=inbox', 'broadcast=12345&folder=inbox', 'broadcast=12345&label=Spam'): response = self.fetchJSON(url, query) self.assertResponseError(response, None, "You may only specify one of the contact, folder, label, broadcast parameters")
def validate_label_name(self, value): if not Label.is_valid_name(value): raise serializers.ValidationError("Name contains illegal characters.") return value
def create_label(self, name, org=None): return Label.get_or_create(org or self.org, self.user, name)
def test_messages(self): url = reverse('api.v2.messages') self.assertEndpointAccess(url) # create some messages joe_msg1 = self.create_msg(direction='I', msg_type='F', text="Howdy", contact=self.joe) frank_msg1 = self.create_msg(direction='I', msg_type='I', text="Bonjour", contact=self.frank, channel=self.twitter) joe_msg2 = self.create_msg(direction='O', msg_type='I', text="How are you?", contact=self.joe, status='Q') frank_msg2 = self.create_msg(direction='O', msg_type='I', text="Ça va?", contact=self.frank, status='D') joe_msg3 = self.create_msg(direction='I', msg_type='F', text="Good", contact=self.joe) frank_msg3 = self.create_msg(direction='I', msg_type='I', text="Bien", contact=self.frank, channel=self.twitter, visibility='A') # add a surveyor message (no URN etc) joe_msg4 = self.create_msg(direction='O', msg_type='F', text="Surveys!", contact=self.joe, contact_urn=None, status='S', channel=None, sent_on=timezone.now()) # add a deleted message self.create_msg(direction='I', msg_type='I', text="!@$!%", contact=self.frank, visibility='D') # add a test contact message self.create_msg(direction='I', msg_type='F', text="Hello", contact=self.test_contact) # add message in other org self.create_msg(direction='I', msg_type='I', text="Guten tag!", contact=self.hans, org=self.org2) # label some of the messages label = Label.get_or_create(self.org, self.admin, "Spam") label.toggle_label([frank_msg1, joe_msg3], add=True) # no filtering with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 6): response = self.fetchJSON(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertResultsById(response, [joe_msg4, frank_msg3, joe_msg3, frank_msg2, joe_msg2, frank_msg1, joe_msg1]) self.assertEqual(response.json['results'][0], { 'id': joe_msg4.pk, 'broadcast': None, 'contact': {'uuid': self.joe.uuid, 'name': self.joe.name}, 'urn': None, 'channel': None, 'direction': "out", 'type': "flow", 'status': "sent", 'archived': False, 'text': "Surveys!", 'labels': [], 'created_on': format_datetime(joe_msg4.created_on), 'sent_on': format_datetime(joe_msg4.sent_on), 'delivered_on': None }) self.assertEqual(response.json['results'][5], { 'id': frank_msg1.pk, 'broadcast': None, 'contact': {'uuid': self.frank.uuid, 'name': self.frank.name}, 'urn': "twitter:franky", 'channel': {'uuid': self.twitter.uuid, 'name': "Twitter Channel"}, 'direction': "in", 'type': "inbox", 'status': "queued", 'archived': False, 'text': "Bonjour", 'labels': [{'uuid': label.uuid, 'name': "Spam"}], 'created_on': format_datetime(frank_msg1.created_on), 'sent_on': None, 'delivered_on': None }) with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 6): # filter by folder (inbox) response = self.fetchJSON(url, 'folder=INBOX') self.assertResultsById(response, [frank_msg1]) # filter by folder (flow) response = self.fetchJSON(url, 'folder=flows') self.assertResultsById(response, [joe_msg3, joe_msg1]) # filter by folder (archived) response = self.fetchJSON(url, 'folder=archived') self.assertResultsById(response, [frank_msg3]) # filter by folder (outbox) response = self.fetchJSON(url, 'folder=outbox') self.assertResultsById(response, [joe_msg2]) # filter by folder (sent) response = self.fetchJSON(url, 'folder=sent') self.assertResultsById(response, [joe_msg4, frank_msg2]) # filter by invalid view response = self.fetchJSON(url, 'folder=invalid') self.assertResultsById(response, []) # filter by id response = self.fetchJSON(url, 'id=%d' % joe_msg3.pk) self.assertResultsById(response, [joe_msg3]) # filter by contact response = self.fetchJSON(url, 'contact=%s' % self.joe.uuid) self.assertResultsById(response, [joe_msg4, joe_msg3, joe_msg2, joe_msg1]) # filter by invalid contact response = self.fetchJSON(url, 'contact=invalid') self.assertResultsById(response, []) # filter by label name response = self.fetchJSON(url, 'label=Spam') self.assertResultsById(response, [joe_msg3, frank_msg1]) # filter by label UUID response = self.fetchJSON(url, 'label=%s' % label.uuid) self.assertResultsById(response, [joe_msg3, frank_msg1]) # filter by invalid label response = self.fetchJSON(url, 'label=invalid') self.assertResultsById(response, []) # can't filter by more than one of contact, folder, label or broadcast together for query in ('contact=%s&label=Spam' % self.joe.uuid, 'label=Spam&folder=inbox', 'broadcast=12345&folder=inbox', 'broadcast=12345&label=Spam'): response = self.fetchJSON(url, query) self.assertResponseError(response, None, "You may only specify one of the contact, folder, label, broadcast parameters")
def validate_label_name(self, value): if not Label.is_valid_name(value): raise serializers.ValidationError( "Name contains illegal characters.") return value
def test_migrate_to_9(self): contact = self.create_contact("Ben Haggerty", phone="+12065552020") # our group and flow to move to uuids group = self.create_group("Phans", []) previous_flow = self.create_flow() start_flow = self.create_flow() label = Label.get_or_create(self.org, self.admin, "My label") substitutions = dict( group_id=group.pk, contact_id=contact.pk, start_flow_id=start_flow.pk, previous_flow_id=previous_flow.pk, label_id=label.pk, ) exported_json = self.get_import_json("migrate_to_9", substitutions) exported_json = migrate_export_to_version_9(exported_json, self.org, True) # our campaign events shouldn't have ids campaign = exported_json["campaigns"][0] event = campaign["events"][0] # campaigns should have uuids self.assertIn("uuid", campaign) self.assertNotIn("id", campaign) # our event flow should be a uuid self.assertIn("flow", event) self.assertIn("uuid", event["flow"]) self.assertNotIn("id", event["flow"]) # our relative field should not have an id self.assertNotIn("id", event["relative_to"]) # evaluate that the flow json is migrated properly flow_json = exported_json["flows"][0] # check that contacts migrated properly send_action = flow_json["action_sets"][0]["actions"][1] self.assertEqual(1, len(send_action["contacts"])) self.assertEqual(0, len(send_action["groups"])) for contact in send_action["contacts"]: self.assertIn("uuid", contact) self.assertNotIn("id", contact) for group in send_action["groups"]: if isinstance(group, dict): self.assertIn("uuid", group) self.assertNotIn("id", group) label_action = flow_json["action_sets"][0]["actions"][2] for label in label_action.get("labels"): self.assertNotIn("id", label) self.assertIn("uuid", label) action_set = flow_json["action_sets"][1] actions = action_set["actions"] for action in actions[0:2]: self.assertIn(action["type"], ("del_group", "add_group")) self.assertIn("uuid", action["groups"][0]) self.assertNotIn("id", action["groups"][0]) for action in actions[2:4]: self.assertIn(action["type"], ("trigger-flow", "flow")) self.assertIn("flow", action) self.assertIn("uuid", action["flow"]) self.assertIn("name", action["flow"]) self.assertNotIn("id", action) self.assertNotIn("name", action) # we also switch flow ids to uuids in the metadata self.assertIn("uuid", flow_json["metadata"]) self.assertNotIn("id", flow_json["metadata"]) # import the same thing again, should have the same uuids new_exported_json = self.get_import_json("migrate_to_9", substitutions) new_exported_json = migrate_export_to_version_9( new_exported_json, self.org, True) self.assertEqual(flow_json["metadata"]["uuid"], new_exported_json["flows"][0]["metadata"]["uuid"]) # but when done as a different site, it should be unique new_exported_json = self.get_import_json("migrate_to_9", substitutions) new_exported_json = migrate_export_to_version_9( new_exported_json, self.org, False) self.assertNotEqual(flow_json["metadata"]["uuid"], new_exported_json["flows"][0]["metadata"]["uuid"]) # can also just import a single flow exported_json = self.get_import_json("migrate_to_9", substitutions) flow_json = migrate_to_version_9(exported_json["flows"][0], start_flow) self.assertIn("uuid", flow_json["metadata"]) self.assertNotIn("id", flow_json["metadata"]) # try it with missing metadata flow_json = self.get_import_json("migrate_to_9", substitutions)["flows"][0] del flow_json["metadata"] flow_json = migrate_to_version_9(flow_json, start_flow) self.assertEqual(1, flow_json["metadata"]["revision"]) self.assertEqual("Color Flow", flow_json["metadata"]["name"]) self.assertEqual(10080, flow_json["metadata"]["expires"]) self.assertIn("uuid", flow_json["metadata"]) # check that our replacements work self.assertEqual("@(CONCAT(parent.divided, parent.sky))", flow_json["action_sets"][0]["actions"][3]["value"]) self.assertEqual("@parent.contact.name", flow_json["action_sets"][0]["actions"][4]["value"])
def validate_name(self, value): if not Label.is_valid_name(value): raise serializers.ValidationError("Name contains illegal characters or is longer than %d characters" % Label.MAX_NAME_LEN) return value