Example #1
0
    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}])
Example #2
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)
Example #3
0
    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()
Example #4
0
    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)
Example #5
0
    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()
Example #6
0
    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)
Example #7
0
    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)
Example #8
0
    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)
Example #9
0
    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)
Example #10
0
    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
Example #11
0
    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
Example #12
0
    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
Example #13
0
 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"})
Example #14
0
    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")
Example #15
0
 def validate_label_name(self, value):
     if not Label.is_valid_name(value):
         raise serializers.ValidationError("Name contains illegal characters.")
     return value
Example #16
0
 def create_label(self, name, org=None):
     return Label.get_or_create(org or self.org, self.user, name)
Example #17
0
    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")
Example #18
0
 def validate_label_name(self, value):
     if not Label.is_valid_name(value):
         raise serializers.ValidationError(
             "Name contains illegal characters.")
     return value
Example #19
0
    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"])
Example #20
0
 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