def customize_form_field(self, name, field): attrs = field.widget.attrs if field.widget.attrs else {} # don't replace the widget if it is already one of us if isinstance(field.widget, (forms.widgets.HiddenInput, CheckboxWidget, InputWidget, SelectWidget, SelectMultipleWidget)): return field if isinstance(field.widget, (forms.widgets.Textarea, )): attrs["textarea"] = True field.widget = InputWidget(attrs=attrs) elif isinstance( field.widget, (forms.widgets.PasswordInput, )): # pragma: needs cover attrs["password"] = True field.widget = InputWidget(attrs=attrs) elif isinstance( field.widget, (forms.widgets.TextInput, forms.widgets.EmailInput, forms.widgets.URLInput, forms.widgets.NumberInput), ): field.widget = InputWidget(attrs=attrs) elif isinstance(field.widget, (forms.widgets.Select, )): if isinstance(field, (forms.models.ModelMultipleChoiceField, )): field.widget = SelectMultipleWidget( attrs) # pragma: needs cover else: field.widget = SelectWidget(attrs) field.widget.choices = field.choices elif isinstance(field.widget, (forms.widgets.CheckboxInput, )): field.widget = CheckboxWidget(attrs) return field
class ScheduleForm(BaseScheduleForm, forms.ModelForm): repeat_period = forms.ChoiceField(choices=Schedule.REPEAT_CHOICES) repeat_days_of_week = forms.MultipleChoiceField( choices=Schedule.REPEAT_DAYS_CHOICES, label="Repeat Days", required=False, widget=SelectMultipleWidget( attrs=({ "placeholder": _("Select days to repeat on") })), ) start_datetime = forms.DateTimeField( required=False, label=_(" "), widget=InputWidget( attrs={ "datetimepicker": True, "placeholder": "Select a time to send the message" }), ) def __init__(self, org, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["start_datetime"].help_text = _("%s Time Zone" % org.timezone) def clean_repeat_days_of_week(self): return "".join(self.cleaned_data["repeat_days_of_week"]) class Meta: model = Schedule fields = "__all__"
class ScheduleFormMixin(forms.Form): start_datetime = forms.DateTimeField( label=_("Start Time"), widget=InputWidget(attrs={ "datetimepicker": True, "placeholder": _("Select a date and time") }), ) repeat_period = forms.ChoiceField(choices=Schedule.REPEAT_CHOICES, label=_("Repeat"), widget=SelectWidget()) repeat_days_of_week = forms.MultipleChoiceField( choices=Schedule.REPEAT_DAYS_CHOICES, label=_("Repeat On Days"), help_text=_("The days of the week to repeat on for weekly schedules"), required=False, widget=SelectMultipleWidget(attrs=({ "placeholder": _("Select days") })), ) def set_user(self, user): """ Because this mixin is mixed with other forms it can't have a __init__ constructor that takes non standard Django forms args and kwargs, so we have to customize based on user after the form has been created. """ tz = user.get_org().timezone self.fields["start_datetime"].help_text = _( "First time this should happen in the %s timezone.") % tz def clean_repeat_days_of_week(self): value = self.cleaned_data["repeat_days_of_week"] # sort by Monday to Sunday value = sorted(value, key=lambda c: Schedule.DAYS_OF_WEEK_OFFSET.index(c)) return "".join(value) def clean(self): cleaned_data = super().clean() if self.is_valid(): if cleaned_data[ "repeat_period"] == Schedule.REPEAT_WEEKLY and not cleaned_data.get( "repeat_days_of_week"): self.add_error("repeat_days_of_week", _("Must specify at least one day of the week.")) return cleaned_data class Meta: fields = ("start_datetime", "repeat_period", "repeat_days_of_week")
class ExportForm(Form): LABEL_CHOICES = ((0, _("Just this label")), (1, _("All messages"))) SYSTEM_LABEL_CHOICES = ((0, _("Just this folder")), (1, _("All messages"))) export_all = forms.ChoiceField( choices=(), label=_("Selection"), initial=0, widget=SelectWidget(attrs={"widget_only": True}) ) start_date = forms.DateField( required=False, help_text=_("Leave blank for the oldest message"), widget=InputWidget(attrs={"datepicker": True, "hide_label": True, "placeholder": _("Start Date")}), ) end_date = forms.DateField( required=False, help_text=_("Leave blank for the latest message"), widget=InputWidget(attrs={"datepicker": True, "hide_label": True, "placeholder": _("End Date")}), ) groups = forms.ModelMultipleChoiceField( queryset=ContactGroup.user_groups.none(), required=False, label=_("Groups"), widget=SelectMultipleWidget( attrs={"widget_only": True, "placeholder": _("Optional: Choose groups to show in your export")} ), ) def __init__(self, user, label, *args, **kwargs): super().__init__(*args, **kwargs) self.user = user self.fields["export_all"].choices = self.LABEL_CHOICES if label else self.SYSTEM_LABEL_CHOICES self.fields["groups"].queryset = ContactGroup.user_groups.filter(org=self.user.get_org(), is_active=True) self.fields["groups"].help_text = _( "Export only messages from these contact groups. " "(Leave blank to export all messages)." ) def clean(self): cleaned_data = super().clean() start_date = cleaned_data.get("start_date") end_date = cleaned_data.get("end_date") if start_date and start_date > date.today(): # pragma: needs cover raise forms.ValidationError(_("Start date can't be in the future.")) if end_date and start_date and end_date < start_date: # pragma: needs cover raise forms.ValidationError(_("End date can't be before start date")) return cleaned_data
class UpdateForm(UpdateTelChannelForm): role = forms.MultipleChoiceField( choices=((Channel.ROLE_RECEIVE, _("Receive")), (Channel.ROLE_SEND, _("Send"))), widget=SelectMultipleWidget(attrs={"widget_only": True}), label=_("Channel Role"), help_text=_("The roles this channel can fulfill"), ) def clean_role(self): return "".join(self.cleaned_data.get("role", [])) class Meta(UpdateTelChannelForm.Meta): fields = "name", "alert_email", "role" readonly = []
class GroupBasedTriggerForm(BaseTriggerForm): groups = forms.ModelMultipleChoiceField( queryset=ContactGroup.user_groups.filter(pk__lt=0), required=False, widget=SelectMultipleWidget( attrs={ "widget_only": True, "placeholder": _( "Optional: Trigger only applies to these groups") }), ) def __init__(self, user, flows, *args, **kwargs): super().__init__(user, flows, *args, **kwargs) self.fields["groups"].queryset = ContactGroup.user_groups.filter( org=self.user.get_org(), is_active=True) def get_existing_triggers(self, cleaned_data): groups = cleaned_data.get("groups", []) org = self.user.get_org() existing = Trigger.objects.filter(org=org, is_archived=False, is_active=True) if groups: existing = existing.filter(groups__in=groups) else: existing = existing.filter(groups=None) if self.instance: existing = existing.exclude(pk=self.instance.pk) return existing class Meta(BaseTriggerForm.Meta): fields = ("flow", "groups")
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")
class ScheduleTriggerForm(BaseScheduleForm, forms.ModelForm): repeat_period = forms.ChoiceField(choices=Schedule.REPEAT_CHOICES, label="Repeat", required=False, widget=SelectWidget()) repeat_days_of_week = forms.MultipleChoiceField( choices=Schedule.REPEAT_DAYS_CHOICES, label="Repeat Days", required=False, widget=SelectMultipleWidget( attrs=({ "placeholder": _("Select days to repeat on") })), ) start_datetime = forms.DateTimeField( required=False, label=_("Start Time"), widget=InputWidget( attrs={ "datetimepicker": True, "placeholder": "Select a time to start the flow" }), ) flow = forms.ModelChoiceField( Flow.objects.filter(pk__lt=0), label=_("Flow"), required=True, widget=SelectWidget(attrs={ "placeholder": _("Select a flow"), "searchable": True }), empty_label=None, ) omnibox = JSONField( label=_("Contacts"), required=True, help_text=_("The groups and contacts the flow will be broadcast to"), widget=OmniboxChoice( attrs={ "placeholder": _("Recipients, enter contacts or groups"), "groups": True, "contacts": True }), ) def __init__(self, user, *args, **kwargs): super().__init__(*args, **kwargs) self.user = user org = user.get_org() flows = Flow.get_triggerable_flows(org) self.fields["start_datetime"].help_text = _("%s Time Zone" % org.timezone) self.fields["flow"].queryset = flows def clean_repeat_days_of_week(self): return "".join(self.cleaned_data["repeat_days_of_week"]) def clean_omnibox(self): return omnibox_deserialize(self.user.get_org(), self.cleaned_data["omnibox"]) class Meta: model = Trigger fields = ("flow", "omnibox", "repeat_period", "repeat_days_of_week", "start_datetime")