示例#1
0
    class Form(BaseTriggerForm):
        channel = TembaChoiceField(
            Channel.objects.none(),
            label=_("Channel"),
            required=False,
            help_text=
            _("The channel to apply this trigger to, leave blank for all Facebook channels"
              ),
        )
        referrer_id = forms.CharField(
            max_length=255,
            required=False,
            label=_("Referrer Id"),
            help_text=_("The referrer id that will trigger us"))

        def __init__(self, user, *args, **kwargs):
            super().__init__(user, Trigger.TYPE_REFERRAL, *args, **kwargs)

            self.fields["channel"].queryset = self.get_channel_choices(
                ContactURN.SCHEMES_SUPPORTING_REFERRALS)

        def get_conflicts_kwargs(self, cleaned_data):
            kwargs = super().get_conflicts_kwargs(cleaned_data)
            kwargs["channel"] = cleaned_data.get("channel")
            kwargs["referrer_id"] = cleaned_data.get("referrer_id", "").strip()
            return kwargs

        class Meta(BaseTriggerForm.Meta):
            fields = ("channel", "referrer_id") + BaseTriggerForm.Meta.fields
示例#2
0
class CampaignForm(forms.ModelForm):
    group = TembaChoiceField(
        queryset=ContactGroup.user_groups.none(),
        empty_label=None,
        widget=SelectWidget(attrs={
            "placeholder": _("Select group"),
            "searchable": True
        }),
        label=_("Group"),
        help_text=
        _("Only contacts in this group will be included in this campaign's events."
          ),
    )

    def __init__(self, user, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields["group"].queryset = ContactGroup.get_user_groups(
            user.get_org(), ready_only=False)

    class Meta:
        model = Campaign
        fields = ("name", "group")
        labels = {"name": _("Name")}
        widgets = {"name": InputWidget()}
示例#3
0
class TestMessageForm(forms.Form):
    channel = TembaChoiceField(Channel.objects.filter(id__lt=0), help_text=_("Which channel will deliver the message"))
    urn = forms.CharField(max_length=14, help_text=_("The URN of the contact delivering this message"))
    text = forms.CharField(max_length=160, widget=forms.Textarea, help_text=_("The message that is being delivered"))

    def __init__(self, *args, **kwargs):  # pragma: needs cover
        org = kwargs["org"]
        del kwargs["org"]

        super().__init__(*args, **kwargs)
        self.fields["channel"].queryset = Channel.objects.filter(org=org, is_active=True)
示例#4
0
    class Form(BaseTriggerForm):
        channel = TembaChoiceField(Channel.objects.none(), label=_("Channel"), required=True)

        def __init__(self, user, *args, **kwargs):
            super().__init__(user, Trigger.TYPE_NEW_CONVERSATION, *args, **kwargs)

            self.fields["channel"].queryset = self.get_channel_choices(ContactURN.SCHEMES_SUPPORTING_NEW_CONVERSATION)

        def get_conflicts_kwargs(self, cleaned_data):
            kwargs = super().get_conflicts_kwargs(cleaned_data)
            kwargs["channel"] = cleaned_data.get("channel")
            return kwargs

        class Meta(BaseTriggerForm.Meta):
            fields = ("channel",) + BaseTriggerForm.Meta.fields
示例#5
0
class BaseTriggerForm(forms.ModelForm):
    """
    Base form for different trigger types
    """

    flow = TembaChoiceField(
        Flow.objects.none(),
        label=_("Flow"),
        required=True,
        widget=SelectWidget(attrs={
            "placeholder": _("Select a flow"),
            "searchable": True
        }),
    )

    groups = TembaMultipleChoiceField(
        queryset=ContactGroup.user_groups.none(),
        label=_("Groups To Include"),
        help_text=_("Only includes contacts in these groups."),
        required=False,
        widget=SelectMultipleWidget(
            attrs={
                "icons": True,
                "placeholder": _("Optional: Select contact groups"),
                "searchable": True
            }),
    )
    exclude_groups = TembaMultipleChoiceField(
        queryset=ContactGroup.user_groups.none(),
        label=_("Groups To Exclude"),
        help_text=_("Excludes contacts in these groups."),
        required=False,
        widget=SelectMultipleWidget(
            attrs={
                "icons": True,
                "placeholder": _("Optional: Select contact groups"),
                "searchable": True
            }),
    )

    def __init__(self, user, trigger_type, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.user = user
        self.org = user.get_org()
        self.trigger_type = Trigger.get_type(code=trigger_type)

        flow_types = self.trigger_type.allowed_flow_types
        flows = self.org.flows.filter(flow_type__in=flow_types,
                                      is_active=True,
                                      is_archived=False,
                                      is_system=False)

        self.fields["flow"].queryset = flows.order_by("name")

        groups = ContactGroup.get_user_groups(self.org, ready_only=False)

        self.fields["groups"].queryset = groups
        self.fields["exclude_groups"].queryset = groups

    def get_channel_choices(self, schemes):
        return self.org.channels.filter(
            is_active=True, schemes__overlap=list(schemes)).order_by("name")

    def get_conflicts(self, cleaned_data):
        conflicts = Trigger.get_conflicts(
            self.org, self.trigger_type.code,
            **self.get_conflicts_kwargs(cleaned_data))

        # if we're editing a trigger we can't conflict with ourselves
        if self.instance:
            conflicts = conflicts.exclude(id=self.instance.id)

        return conflicts

    def get_conflicts_kwargs(self, cleaned_data):
        return {"groups": cleaned_data.get("groups", [])}

    def clean_keyword(self):
        keyword = self.cleaned_data.get("keyword") or ""
        keyword = keyword.strip()

        if not self.trigger_type.is_valid_keyword(keyword):
            raise forms.ValidationError(
                _("Must be a single word containing only letters and numbers, or a single emoji character."
                  ))

        return keyword.lower()

    def clean(self):
        cleaned_data = super().clean()

        groups = cleaned_data.get("groups", [])
        exclude_groups = cleaned_data.get("exclude_groups", [])

        if set(groups).intersection(exclude_groups):
            raise forms.ValidationError(
                _("Can't include and exclude the same group."))

        # only check for conflicts if user is submitting valid data for all fields
        if not self.errors and self.get_conflicts(cleaned_data):
            raise forms.ValidationError(
                _("There already exists a trigger of this type with these options."
                  ))

        return cleaned_data

    class Meta:
        model = Trigger
        fields = ("flow", "groups", "exclude_groups")
示例#6
0
class CampaignEventForm(forms.ModelForm):

    event_type = forms.ChoiceField(
        choices=((CampaignEvent.TYPE_MESSAGE, "Send a message"), (CampaignEvent.TYPE_FLOW, "Start a flow")),
        required=True,
        widget=SelectWidget(attrs={"placeholder": _("Select the event type"), "widget_only": True}),
    )

    direction = forms.ChoiceField(
        choices=(("B", "Before"), ("A", "After")),
        required=True,
        widget=SelectWidget(attrs={"placeholder": _("Relative date direction"), "widget_only": True}),
    )

    unit = forms.ChoiceField(
        choices=CampaignEvent.UNIT_CHOICES,
        required=True,
        widget=SelectWidget(attrs={"placeholder": _("Select a unit"), "widget_only": True}),
    )

    flow_to_start = TembaChoiceField(
        queryset=Flow.objects.filter(is_active=True),
        required=False,
        empty_label=None,
        widget=SelectWidget(
            attrs={
                "placeholder": _("Select a flow to start"),
                "widget_only": True,
                "searchable": True,
            }
        ),
    )

    relative_to = TembaChoiceField(
        queryset=ContactField.all_fields.none(),
        required=False,
        empty_label=None,
        widget=SelectWidget(
            attrs={
                "placeholder": _("Select a date field to base this event on"),
                "widget_only": True,
                "searchable": True,
            }
        ),
    )

    delivery_hour = forms.ChoiceField(
        choices=CampaignEvent.get_hour_choices(),
        required=False,
        widget=SelectWidget(attrs={"placeholder": _("Select hour for delivery"), "widget_only": True}),
    )

    flow_start_mode = forms.ChoiceField(
        choices=(
            (CampaignEvent.MODE_INTERRUPT, _("Stop it and run this flow")),
            (CampaignEvent.MODE_SKIP, _("Skip this event")),
        ),
        required=False,
        widget=SelectWidget(attrs={"widget_only": True}),
    )

    message_start_mode = forms.ChoiceField(
        choices=(
            (CampaignEvent.MODE_INTERRUPT, _("Stop it and send the message")),
            (CampaignEvent.MODE_SKIP, _("Skip this message")),
            (CampaignEvent.MODE_PASSIVE, _("Send the message")),
        ),
        required=False,
        widget=SelectWidget(attrs={"widget_only": True}),
    )

    def clean(self):
        data = super().clean()

        if self.data["event_type"] == CampaignEvent.TYPE_MESSAGE:
            if self.languages:
                language = self.languages[0].language
                iso_code = language["iso_code"]
                if iso_code not in self.data or not self.data[iso_code].strip():
                    raise ValidationError(_("A message is required for '%s'") % language["name"])

                for lang_data in self.languages:
                    lang = lang_data.language
                    iso_code = lang["iso_code"]
                    if iso_code in self.data and len(self.data[iso_code].strip()) > Msg.MAX_TEXT_LEN:
                        raise ValidationError(
                            _("Translation for '%(language)s' exceeds the %(limit)d character limit.")
                            % dict(language=lang["name"], limit=Msg.MAX_TEXT_LEN)
                        )
            if not data.get("message_start_mode"):
                self.add_error("message_start_mode", _("This field is required."))
        else:
            if not data.get("flow_to_start"):
                self.add_error("flow_to_start", _("This field is required."))
            if not data.get("flow_start_mode"):
                self.add_error("flow_start_mode", _("This field is required."))

        return data

    def pre_save(self, request, obj):
        org = self.user.get_org()

        # if it's before, negate the offset
        if self.cleaned_data["direction"] == "B":
            obj.offset = -obj.offset

        if self.cleaned_data["unit"] == "H" or self.cleaned_data["unit"] == "M":  # pragma: needs cover
            obj.delivery_hour = -1

        # if its a message flow, set that accordingly
        if self.cleaned_data["event_type"] == CampaignEvent.TYPE_MESSAGE:

            if self.instance.id:
                base_language = self.instance.flow.base_language
            else:
                base_language = org.flow_languages[0] if org.flow_languages else "base"

            translations = {}
            for language in self.languages:
                iso_code = language.language["iso_code"]
                if iso_code in self.cleaned_data and self.cleaned_data.get(iso_code, "").strip():
                    translations[iso_code] = self.cleaned_data.get(iso_code, "").strip()

            if not obj.flow_id or not obj.flow.is_active or not obj.flow.is_system:
                obj.flow = Flow.create_single_message(org, request.user, translations, base_language=base_language)
            else:
                # set our single message on our flow
                obj.flow.update_single_message_flow(self.user, translations, base_language)

            obj.message = translations
            obj.full_clean()
            obj.start_mode = self.cleaned_data["message_start_mode"]

        # otherwise, it's an event that runs an existing flow
        else:
            obj.flow = self.cleaned_data["flow_to_start"]
            obj.start_mode = self.cleaned_data["flow_start_mode"]

            # force passive mode for user-selected background flows
            if obj.flow.flow_type == Flow.TYPE_BACKGROUND:
                obj.start_mode = CampaignEvent.MODE_PASSIVE

    def __init__(self, user, event, *args, **kwargs):
        self.user = user
        super().__init__(*args, **kwargs)

        org = self.user.get_org()

        relative_to = self.fields["relative_to"]
        relative_to.queryset = ContactField.all_fields.filter(
            org=org, is_active=True, value_type=ContactField.TYPE_DATETIME
        ).order_by("label")

        flow = self.fields["flow_to_start"]
        flow.queryset = Flow.objects.filter(
            org=self.user.get_org(),
            flow_type__in=[Flow.TYPE_MESSAGE, Flow.TYPE_VOICE, Flow.TYPE_BACKGROUND],
            is_active=True,
            is_archived=False,
            is_system=False,
        ).order_by("name")

        if event and event.flow and event.flow.flow_type == Flow.TYPE_BACKGROUND:
            flow.widget.attrs["info_text"] = CampaignEventCRUDL.BACKGROUND_WARNING

        message = self.instance.message or {}
        self.languages = []

        # add in all of our languages for message forms
        for lang_code in org.flow_languages:
            lang_name = languages.get_name(lang_code)
            insert = None

            # if it's our primary language, allow use to steal the 'base' message
            if org.flow_languages and org.flow_languages[0] == lang_code:
                initial = message.get(lang_code, "")

                if not initial:
                    initial = message.get("base", "")

                # also, let's show it first
                insert = 0
            else:
                # otherwise, its just a normal language
                initial = message.get(lang_code, "")

            field = forms.CharField(
                widget=CompletionTextarea(
                    attrs={
                        "placeholder": _(
                            "Hi @contact.name! This is just a friendly reminder to apply your fertilizer."
                        ),
                        "widget_only": True,
                    }
                ),
                required=False,
                label=lang_name,
                initial=initial,
            )

            self.fields[lang_code] = field
            field.language = dict(name=lang_name, iso_code=lang_code)

            # see if we need to insert or append
            if insert is not None:
                self.languages.insert(insert, field)
            else:
                self.languages.append(field)

        # determine our base language if necessary
        base_language = org.flow_languages[0] if org.flow_languages else "base"

        # if we are editing, always include the flow base language
        if self.instance.id:
            base_language = self.instance.flow.base_language

        # add our default language, we'll insert it at the front of the list
        if base_language and base_language not in self.fields:
            field = forms.CharField(
                widget=CompletionTextarea(
                    attrs={
                        "placeholder": _(
                            "Hi @contact.name! This is just a friendly reminder to apply your fertilizer."
                        ),
                        "widget_only": True,
                    }
                ),
                required=False,
                label=_("Default"),
                initial=message.get(base_language),
            )

            self.fields[base_language] = field
            field.language = dict(iso_code=base_language, name="Default")
            self.languages.insert(0, field)

    class Meta:
        model = CampaignEvent
        fields = "__all__"
        widgets = {"offset": InputWidget(attrs={"widget_only": True})}