Esempio n. 1
0
class AddStructuredKeywordView(BaseMessagingSectionView):
    urlname = 'add_structured_keyword'
    page_title = ugettext_noop("New Structured Keyword")
    template_name = 'reminders/keyword.html'
    process_structured_message = True

    @method_decorator(requires_privilege_with_fallback(privileges.INBOUND_SMS))
    def dispatch(self, *args, **kwargs):
        return super(AddStructuredKeywordView, self).dispatch(*args, **kwargs)

    @property
    def parent_pages(self):
        return [
            {
                'title': KeywordsListView.page_title,
                'url': reverse(KeywordsListView.urlname, args=[self.domain]),
            },
        ]

    @property
    @memoized
    def keyword(self):
        return Keyword(domain=self.domain)

    @property
    def keyword_form(self):
        raise NotImplementedError("you must implement keyword_form")

    @property
    def page_context(self):
        return {
            'form': self.keyword_form,
            'form_list': get_form_list(self.domain),
        }

    @property
    @memoized
    def keyword_form(self):
        if self.request.method == 'POST':
            return KeywordForm(
                self.request.POST, domain=self.domain,
                process_structured=self.process_structured_message,
            )
        return KeywordForm(
            domain=self.domain,
            process_structured=self.process_structured_message,
        )

    def post(self, request, *args, **kwargs):
        if self.keyword_form.is_valid():
            with transaction.atomic():
                self.keyword.keyword = self.keyword_form.cleaned_data['keyword']
                self.keyword.description = self.keyword_form.cleaned_data['description']
                self.keyword.delimiter = self.keyword_form.cleaned_data['delimiter']
                self.keyword.override_open_sessions = self.keyword_form.cleaned_data['override_open_sessions']

                self.keyword.initiator_doc_type_filter = []
                if self.keyword_form.cleaned_data['allow_keyword_use_by'] == 'users':
                    self.keyword.initiator_doc_type_filter.append('CommCareUser')
                if self.keyword_form.cleaned_data['allow_keyword_use_by'] == 'cases':
                    self.keyword.initiator_doc_type_filter.append('CommCareCase')

                self.keyword.save()

                self.keyword.keywordaction_set.all().delete()
                if self.keyword_form.cleaned_data['sender_content_type'] != NO_RESPONSE:
                    self.keyword.keywordaction_set.create(
                        recipient=KeywordAction.RECIPIENT_SENDER,
                        action=self.keyword_form.cleaned_data['sender_content_type'],
                        message_content=self.keyword_form.cleaned_data['sender_message'],
                        form_unique_id=self.keyword_form.cleaned_data['sender_form_unique_id'],
                    )
                if self.process_structured_message:
                    self.keyword.keywordaction_set.create(
                        recipient=KeywordAction.RECIPIENT_SENDER,
                        action=KeywordAction.ACTION_STRUCTURED_SMS,
                        form_unique_id=self.keyword_form.cleaned_data['structured_sms_form_unique_id'],
                        use_named_args=self.keyword_form.cleaned_data['use_named_args'],
                        named_args=self.keyword_form.cleaned_data['named_args'],
                        named_args_separator=self.keyword_form.cleaned_data['named_args_separator'],
                    )
                if self.keyword_form.cleaned_data['other_recipient_content_type'] != NO_RESPONSE:
                    self.keyword.keywordaction_set.create(
                        recipient=self.keyword_form.cleaned_data['other_recipient_type'],
                        recipient_id=self.keyword_form.cleaned_data['other_recipient_id'],
                        action=self.keyword_form.cleaned_data['other_recipient_content_type'],
                        message_content=self.keyword_form.cleaned_data['other_recipient_message'],
                        form_unique_id=self.keyword_form.cleaned_data['other_recipient_form_unique_id'],
                    )

                return HttpResponseRedirect(reverse(KeywordsListView.urlname, args=[self.domain]))
        return self.get(request, *args, **kwargs)
Esempio n. 2
0
class CreateConditionalAlertView(BaseMessagingSectionView, AsyncHandlerMixin):
    urlname = 'create_conditional_alert'
    page_title = ugettext_lazy('New Conditional Alert')
    template_name = 'scheduling/conditional_alert.html'
    async_handlers = [ConditionalAlertAsyncHandler]
    read_only_mode = False

    @property
    def help_text(self):
        return _("""
            For information on Conditional Alerts, see the
            <a target="_blank" href="https://confluence.dimagi.com/display/commcarepublic/Conditional+Alerts">
                Conditional Alerts
            </a>
            help page.
        """)

    @method_decorator(
        requires_privilege_with_fallback(privileges.REMINDERS_FRAMEWORK))
    @use_jquery_ui
    @use_timepicker
    def dispatch(self, *args, **kwargs):
        return super(CreateConditionalAlertView,
                     self).dispatch(*args, **kwargs)

    @property
    def parent_pages(self):
        return [
            {
                'title':
                ConditionalAlertListView.page_title,
                'url':
                reverse(ConditionalAlertListView.urlname, args=[self.domain]),
            },
        ]

    @property
    def page_context(self):
        context = super().page_context
        context.update({
            'basic_info_form': self.basic_info_form,
            'criteria_form': self.criteria_form,
            'help_text': self.help_text,
            'schedule_form': self.schedule_form,
            'read_only_mode': self.read_only_mode,
            'is_system_admin': self.is_system_admin,
            'criteria_form_active': False,
            'schedule_form_active': False,
            'new_rule': not bool(self.rule),
            'rule_name': self.rule.name if self.rule else '',
        })

        if self.request.method == 'POST':
            context.update({
                'criteria_form_active':
                not self.criteria_form.is_valid()
                or self.schedule_form.is_valid(),
                'schedule_form_active':
                not self.schedule_form.is_valid()
                and self.criteria_form.is_valid(),
                'rule_name':
                self.basic_info_form.rule_name,
            })

        return context

    @cached_property
    def schedule_form(self):
        args = [
            self.domain,
            self.schedule,
            self.can_use_inbound_sms,
            self.rule,
            self.criteria_form,
        ]

        if self.request.method == 'POST':
            args.append(self.request.POST)

        return ConditionalAlertScheduleForm(
            *args, is_system_admin=self.is_system_admin)

    @property
    def schedule(self):
        return None

    @property
    def rule(self):
        return None

    @cached_property
    def basic_info_form(self):
        if self.request.method == 'POST':
            return ConditionalAlertForm(self.domain, self.rule,
                                        self.request.POST)

        return ConditionalAlertForm(self.domain, self.rule)

    @cached_property
    def criteria_form(self):
        kwargs = {
            'rule': self.rule,
            'is_system_admin': self.is_system_admin,
        }

        if self.request.method == 'POST':
            return ConditionalAlertCriteriaForm(self.domain, self.request.POST,
                                                **kwargs)

        return ConditionalAlertCriteriaForm(self.domain, **kwargs)

    def post(self, request, *args, **kwargs):
        if self.async_response is not None:
            return self.async_response

        basic_info_form_valid = self.basic_info_form.is_valid()
        criteria_form_valid = self.criteria_form.is_valid()
        schedule_form_valid = self.schedule_form.is_valid()

        if self.read_only_mode:
            # Don't allow making changes to rules that have custom
            # criteria/actions unless the user has permission to
            return HttpResponseBadRequest()

        if basic_info_form_valid and criteria_form_valid and schedule_form_valid:
            if not self.is_system_admin and (
                    self.criteria_form.requires_system_admin_to_save
                    or self.schedule_form.requires_system_admin_to_save):
                # Don't allow adding custom criteria/actions to rules
                # unless the user has permission to
                return HttpResponseBadRequest()

            if not self.can_use_inbound_sms and self.schedule_form.uses_sms_survey:
                return HttpResponseBadRequest(
                    "Cannot create or edit survey reminders because subscription "
                    "does not have access to inbound SMS")

            with transaction.atomic():
                if self.rule:
                    rule = self.rule
                else:
                    rule = AutomaticUpdateRule(
                        domain=self.domain,
                        active=True,
                        workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING,
                    )

                rule.name = self.basic_info_form.cleaned_data['name']
                self.criteria_form.save_criteria(rule)
                self.schedule_form.save_rule_action_and_schedule(rule)

            initiate_messaging_rule_run(rule)
            return HttpResponseRedirect(
                reverse(ConditionalAlertListView.urlname, args=[self.domain]))

        return self.get(request, *args, **kwargs)
Esempio n. 3
0
class BulkSMSVerificationView(BaseDomainView):
    urlname = 'bulk_sms_verification'

    @method_decorator(require_can_edit_commcare_users)
    @method_decorator(requires_privilege_with_fallback(privileges.INBOUND_SMS))
    def dispatch(self, *args, **kwargs):
        return super(BulkSMSVerificationView, self).dispatch(*args, **kwargs)

    def initiate_verification(self, request, group):
        counts = {
            'users': 0,
            'phone_numbers': 0,
            'phone_numbers_in_use': 0,
            'phone_numbers_already_verified': 0,
            'phone_numbers_pending_verification': 0,
            'workflows_started': 0,
        }
        users_with_error = []

        for user in group.get_users(is_active=True, only_commcare=True):
            counts['users'] += 1
            for phone_number in user.phone_numbers:
                counts['phone_numbers'] += 1
                try:
                    result = initiate_sms_verification_workflow(
                        user, phone_number)
                except Exception:
                    result = None

                if result is None:
                    users_with_error.append(user.raw_username)
                elif result == VERIFICATION__ALREADY_IN_USE:
                    counts['phone_numbers_in_use'] += 1
                elif result == VERIFICATION__ALREADY_VERIFIED:
                    counts['phone_numbers_already_verified'] += 1
                elif result == VERIFICATION__RESENT_PENDING:
                    counts['phone_numbers_pending_verification'] += 1
                elif result == VERIFICATION__WORKFLOW_STARTED:
                    counts['workflows_started'] += 1

        success_msg = _(
            '%(users)s user(s) and %(phone_numbers)s phone number(s) processed. '
            '%(phone_numbers_already_verified)s already verified, '
            '%(phone_numbers_in_use)s already in use by other contact(s), '
            '%(phone_numbers_pending_verification)s already pending verification, '
            'and %(workflows_started)s verification workflow(s) started.'
        ) % counts
        messages.success(request, success_msg)
        if users_with_error:
            error_msg = _(
                'Error processing the following user(s): %(users)s. Please try '
                'again and if the problem persists, report an issue.') % {
                    'users': ', '.join(set(users_with_error))
                }
            messages.error(request, error_msg)

    def get(self, request, *args, **kwargs):
        raise Http404()

    def post(self, request, domain, group_id, *args, **kwargs):
        group = get_group_or_404(domain, group_id)
        self.initiate_verification(request, group)
        return HttpResponseRedirect(
            reverse(EditGroupMembersView.urlname, args=[domain, group_id]))
Esempio n. 4
0
class CreateCommCareUserModal(JsonRequestResponseMixin, DomainViewMixin, View):
    template_name = "users/new_mobile_worker_modal.html"
    urlname = 'new_mobile_worker_modal'

    @method_decorator(require_can_edit_commcare_users)
    def dispatch(self, request, *args, **kwargs):
        if not can_add_extra_mobile_workers(request):
            raise PermissionDenied()
        return super(CreateCommCareUserModal, self).dispatch(request, *args, **kwargs)

    def render_form(self, status):
        return self.render_json_response({
            "status": status,
            "form_html": render_to_string(self.template_name, {
                'form': self.new_commcare_user_form,
                'data_fields_form': self.custom_data.form,
            }, request=self.request)
        })

    def get(self, request, *args, **kwargs):
        return self.render_form("success")

    @property
    @memoized
    def custom_data(self):
        return CustomDataEditor(
            field_view=UserFieldsView,
            domain=self.domain,
            post_dict=self.request.POST if self.request.method == "POST" else None,
        )

    @property
    @memoized
    def new_commcare_user_form(self):
        if self.request.method == "POST":
            data = self.request.POST.dict()
            form = CommCareAccountForm(data, domain=self.domain)
        else:
            form = CommCareAccountForm(domain=self.domain)
        return form

    @method_decorator(requires_privilege_with_fallback(privileges.OUTBOUND_SMS))
    def post(self, request, *args, **kwargs):
        if self.new_commcare_user_form.is_valid() and self.custom_data.is_valid():
            username = self.new_commcare_user_form.cleaned_data['username']
            password = self.new_commcare_user_form.cleaned_data['password']
            phone_number = self.new_commcare_user_form.cleaned_data['phone_number']

            user = CommCareUser.create(
                self.domain,
                username,
                password,
                phone_number=phone_number,
                device_id="Generated from HQ",
                user_data=self.custom_data.get_data_to_save(),
            )

            if 'location_id' in request.GET:
                try:
                    loc = SQLLocation.objects.get(domain=self.domain,
                                                  location_id=request.GET['location_id'])
                except SQLLocation.DoesNotExist:
                    raise Http404()
                user.set_location(loc)

            if phone_number:
                initiate_sms_verification_workflow(user, phone_number)

            user_json = {'user_id': user._id, 'text': user.username_in_report}
            return self.render_json_response({"status": "success",
                                              "user": user_json})
        return self.render_form("failure")
Esempio n. 5
0
class BroadcastListView(BaseMessagingSectionView):
    template_name = 'scheduling/broadcasts_list.html'
    urlname = 'new_list_broadcasts'
    page_title = ugettext_lazy('Broadcasts')

    LIST_SCHEDULED = 'list_scheduled'
    LIST_IMMEDIATE = 'list_immediate'
    ACTION_ACTIVATE_SCHEDULED_BROADCAST = 'activate_scheduled_broadcast'
    ACTION_DEACTIVATE_SCHEDULED_BROADCAST = 'deactivate_scheduled_broadcast'
    ACTION_DELETE_SCHEDULED_BROADCAST = 'delete_scheduled_broadcast'

    @method_decorator(
        requires_privilege_with_fallback(privileges.REMINDERS_FRAMEWORK))
    @use_datatables
    def dispatch(self, *args, **kwargs):
        return super(BroadcastListView, self).dispatch(*args, **kwargs)

    @cached_property
    def project_timezone(self):
        return get_timezone_for_user(None, self.domain)

    def _format_time(self, time):
        if not time:
            return ''

        user_time = ServerTime(time).user_time(self.project_timezone)
        return user_time.ui_string(SERVER_DATETIME_FORMAT)

    def get_scheduled_ajax_response(self):
        query = (ScheduledBroadcast.objects.filter(domain=self.domain,
                                                   deleted=False).order_by(
                                                       '-last_sent_timestamp',
                                                       'id'))
        total_records = query.count()
        query = query.select_related('schedule')
        limit = int(self.request.GET.get('limit', 10))
        page = int(self.request.GET.get('page', 1))
        skip = (page - 1) * limit

        broadcasts = [
            self._fmt_scheduled_broadcast(broadcast)
            for broadcast in query[skip:skip + limit]
        ]
        return JsonResponse({
            'broadcasts': broadcasts,
            'total': total_records,
        })

    def _fmt_scheduled_broadcast(self, broadcast):
        return {
            'name': broadcast.name,
            'last_sent': self._format_time(broadcast.last_sent_timestamp),
            'active': broadcast.schedule.active,
            'editable': self.can_use_inbound_sms
            or not broadcast.schedule.memoized_uses_sms_survey,
            'id': broadcast.id,
            'deleted': broadcast.deleted,
        }

    def get_immediate_ajax_response(self):
        query = (ImmediateBroadcast.objects.filter(domain=self.domain,
                                                   deleted=False).order_by(
                                                       '-last_sent_timestamp',
                                                       'id'))
        total_records = query.count()
        limit = int(self.request.GET.get('limit', 10))
        page = int(self.request.GET.get('page', 1))
        skip = (page - 1) * limit

        broadcasts = [{
            'name':
            broadcast.name,
            'last_sent':
            self._format_time(broadcast.last_sent_timestamp),
            'id':
            broadcast.id,
        } for broadcast in query[skip:skip + limit]]

        return JsonResponse({
            'broadcasts': broadcasts,
            'total': total_records,
        })

    def get_scheduled_broadcast(self, broadcast_id):
        try:
            return ScheduledBroadcast.objects.get(domain=self.domain,
                                                  pk=broadcast_id,
                                                  deleted=False)
        except ScheduledBroadcast.DoesNotExist:
            raise Http404()

    def get_scheduled_broadcast_activate_ajax_response(self, active_flag,
                                                       broadcast_id):
        broadcast = self.get_scheduled_broadcast(broadcast_id)
        if not self.can_use_inbound_sms and broadcast.schedule.memoized_uses_sms_survey:
            return HttpResponseBadRequest(
                "Cannot create or edit survey reminders because subscription "
                "does not have access to inbound SMS")

        TimedSchedule.objects.filter(schedule_id=broadcast.schedule_id).update(
            active=active_flag)
        refresh_timed_schedule_instances.delay(broadcast.schedule_id,
                                               broadcast.recipients,
                                               start_date=broadcast.start_date)

        return JsonResponse({
            'success':
            True,
            'broadcast':
            self._fmt_scheduled_broadcast(broadcast),
        })

    def get_scheduled_broadcast_delete_ajax_response(self, broadcast_id):
        broadcast = self.get_scheduled_broadcast(broadcast_id)
        broadcast.soft_delete()
        return JsonResponse({
            'success':
            True,
            'broadcast':
            self._fmt_scheduled_broadcast(broadcast),
        })

    def get(self, request, *args, **kwargs):
        action = request.GET.get('action')
        if action == self.LIST_SCHEDULED:
            return self.get_scheduled_ajax_response()
        elif action == self.LIST_IMMEDIATE:
            return self.get_immediate_ajax_response()

        return super(BroadcastListView, self).get(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        action = request.POST.get('action')
        broadcast_id = request.POST.get('broadcast_id')

        with get_broadcast_edit_critical_section(
                EditScheduleView.SCHEDULED_BROADCAST, broadcast_id):
            if action == self.ACTION_ACTIVATE_SCHEDULED_BROADCAST:
                return self.get_scheduled_broadcast_activate_ajax_response(
                    True, broadcast_id)
            elif action == self.ACTION_DEACTIVATE_SCHEDULED_BROADCAST:
                return self.get_scheduled_broadcast_activate_ajax_response(
                    False, broadcast_id)
            elif action == self.ACTION_DELETE_SCHEDULED_BROADCAST:
                return self.get_scheduled_broadcast_delete_ajax_response(
                    broadcast_id)
            else:
                return HttpResponseBadRequest()
Esempio n. 6
0
class CreateScheduleView(BaseMessagingSectionView, AsyncHandlerMixin):
    urlname = 'create_schedule'
    page_title = ugettext_lazy('Schedule a Message')
    template_name = 'scheduling/create_schedule.html'
    async_handlers = [MessagingRecipientHandler]
    read_only_mode = False

    @method_decorator(_requires_new_reminder_framework())
    @method_decorator(requires_privilege_with_fallback(privileges.OUTBOUND_SMS)
                      )
    @method_decorator(require_permission(Permissions.edit_data))
    @use_jquery_ui
    @use_timepicker
    @use_select2
    def dispatch(self, *args, **kwargs):
        return super(CreateScheduleView, self).dispatch(*args, **kwargs)

    @property
    def parent_pages(self):
        return [
            {
                'title': BroadcastListView.page_title,
                'url': reverse(BroadcastListView.urlname, args=[self.domain]),
            },
        ]

    @property
    def broadcast(self):
        return None

    @property
    def schedule(self):
        return None

    @cached_property
    def schedule_form(self):
        if self.request.method == 'POST':
            return BroadcastForm(self.domain, self.schedule, self.broadcast,
                                 self.request.POST)

        return BroadcastForm(self.domain, self.schedule, self.broadcast)

    @property
    def page_context(self):
        return {
            'schedule_form': self.schedule_form,
            'read_only_mode': self.read_only_mode,
        }

    def post(self, request, *args, **kwargs):
        if self.async_response is not None:
            return self.async_response

        if self.schedule_form.is_valid():
            broadcast, schedule = self.schedule_form.save_broadcast_and_schedule(
            )
            if isinstance(schedule, AlertSchedule):
                refresh_alert_schedule_instances.delay(schedule.schedule_id,
                                                       broadcast.recipients)
            elif isinstance(schedule, TimedSchedule):
                refresh_timed_schedule_instances.delay(
                    schedule.schedule_id,
                    broadcast.recipients,
                    start_date=broadcast.start_date)
            else:
                raise TypeError("Expected AlertSchedule or TimedSchedule")

            return HttpResponseRedirect(
                reverse(BroadcastListView.urlname, args=[self.domain]))

        return self.get(request, *args, **kwargs)
Esempio n. 7
0
class CreateConditionalAlertView(BaseMessagingSectionView, AsyncHandlerMixin):
    urlname = 'create_conditional_alert'
    page_title = ugettext_lazy('New Conditional Message')
    template_name = 'scheduling/conditional_alert.html'
    async_handlers = [MessagingRecipientHandler]

    @method_decorator(_requires_new_reminder_framework())
    @method_decorator(requires_privilege_with_fallback(privileges.OUTBOUND_SMS)
                      )
    @method_decorator(require_permission(Permissions.edit_data))
    @use_jquery_ui
    @use_timepicker
    @use_select2
    def dispatch(self, *args, **kwargs):
        return super(CreateConditionalAlertView,
                     self).dispatch(*args, **kwargs)

    @property
    def parent_pages(self):
        return [
            {
                'title':
                ConditionalAlertListView.page_title,
                'url':
                reverse(ConditionalAlertListView.urlname, args=[self.domain]),
            },
        ]

    @property
    def page_context(self):
        return {
            'basic_info_form':
            self.basic_info_form,
            'criteria_form':
            self.criteria_form,
            'schedule_form':
            self.schedule_form,
            'read_only_mode':
            self.read_only_mode,
            'criteria_form_active':
            self.criteria_form.errors or not self.schedule_form.errors,
            'schedule_form_active':
            self.schedule_form.errors and not self.criteria_form.errors,
        }

    @cached_property
    def schedule_form(self):
        if self.request.method == 'POST':
            return ConditionalAlertScheduleForm(self.domain, self.schedule,
                                                self.rule, self.criteria_form,
                                                self.request.POST)

        return ConditionalAlertScheduleForm(self.domain, self.schedule,
                                            self.rule, self.criteria_form)

    @property
    def schedule(self):
        return None

    @property
    def rule(self):
        return None

    @cached_property
    def read_only_mode(self):
        return (not self.is_system_admin
                and (self.criteria_form.requires_system_admin_to_edit
                     or self.schedule_form.requires_system_admin_to_edit))

    @cached_property
    def is_system_admin(self):
        return self.request.couch_user.is_superuser

    @cached_property
    def basic_info_form(self):
        if self.request.method == 'POST':
            return ConditionalAlertForm(self.domain, self.rule,
                                        self.request.POST)

        return ConditionalAlertForm(self.domain, self.rule)

    @cached_property
    def criteria_form(self):
        kwargs = {
            'rule': self.rule,
            'is_system_admin': self.is_system_admin,
        }

        if self.request.method == 'POST':
            return ConditionalAlertCriteriaForm(self.domain, self.request.POST,
                                                **kwargs)

        return ConditionalAlertCriteriaForm(self.domain, **kwargs)

    def post(self, request, *args, **kwargs):
        if self.async_response is not None:
            return self.async_response

        basic_info_form_valid = self.basic_info_form.is_valid()
        criteria_form_valid = self.criteria_form.is_valid()
        schedule_form_valid = self.schedule_form.is_valid()

        if self.read_only_mode:
            # Don't allow making changes to rules that have custom
            # criteria/actions unless the user has permission to
            return HttpResponseBadRequest()

        if basic_info_form_valid and criteria_form_valid and schedule_form_valid:
            if not self.is_system_admin and (
                    self.criteria_form.requires_system_admin_to_save
                    or self.schedule_form.requires_system_admin_to_save):
                # Don't allow adding custom criteria/actions to rules
                # unless the user has permission to
                return HttpResponseBadRequest()

            with transaction.atomic():
                if self.rule:
                    rule = self.rule
                else:
                    rule = AutomaticUpdateRule(
                        domain=self.domain,
                        active=True,
                        migrated=True,
                        workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING,
                    )

                rule.name = self.basic_info_form.cleaned_data['name']
                self.criteria_form.save_criteria(rule)
                self.schedule_form.save_rule_action_and_schedule(rule)

            initiate_messaging_rule_run(rule.domain, rule.pk)
            return HttpResponseRedirect(
                reverse(ConditionalAlertListView.urlname, args=[self.domain]))

        return self.get(request, *args, **kwargs)
Esempio n. 8
0
class UploadCommCareUsers(BaseManageCommCareUserView):
    template_name = 'hqwebapp/bulk_upload.html'
    urlname = 'upload_commcare_users'
    page_title = ugettext_noop("Bulk Upload Mobile Workers")

    @method_decorator(requires_privilege_with_fallback(privileges.BULK_USER_MANAGEMENT))
    def dispatch(self, request, *args, **kwargs):
        return super(UploadCommCareUsers, self).dispatch(request, *args, **kwargs)

    @property
    def page_context(self):
        request_params = self.request.GET if self.request.method == 'GET' else self.request.POST
        context = {
            'bulk_upload': {
                "help_site": {
                    "address": BULK_MOBILE_HELP_SITE,
                    "name": _("CommCare Help Site"),
                },
                "download_url": reverse(
                    "download_commcare_users", args=(self.domain,)),
                "adjective": _("mobile worker"),
                "plural_noun": _("mobile workers"),
            },
            'show_secret_settings': request_params.get("secret", False),
        }
        context.update({
            'bulk_upload_form': get_bulk_upload_form(context),
        })
        return context

    def post(self, request, *args, **kwargs):
        """View's dispatch method automatically calls this"""
        try:
            self.workbook = get_workbook(request.FILES.get('bulk_upload_file'))
        except WorkbookJSONError as e:
            messages.error(request, six.text_type(e))
            return self.get(request, *args, **kwargs)

        try:
            self.user_specs = self.workbook.get_worksheet(title='users')
        except WorksheetNotFound:
            try:
                self.user_specs = self.workbook.get_worksheet()
            except WorksheetNotFound:
                return HttpResponseBadRequest("Workbook has no worksheets")

        try:
            self.group_specs = self.workbook.get_worksheet(title='groups')
        except WorksheetNotFound:
            self.group_specs = []

        try:
            check_headers(self.user_specs)
        except UserUploadError as e:
            messages.error(request, _(six.text_type(e)))
            return HttpResponseRedirect(reverse(UploadCommCareUsers.urlname, args=[self.domain]))

        # convert to list here because iterator destroys the row once it has
        # been read the first time
        self.user_specs = list(self.user_specs)

        for user_spec in self.user_specs:
            try:
                user_spec['username'] = enforce_string_type(user_spec['username'])
            except StringTypeRequiredError:
                messages.error(
                    request,
                    _("Error: Expected username to be a Text type for username {0}")
                    .format(user_spec['username'])
                )
                return HttpResponseRedirect(reverse(UploadCommCareUsers.urlname, args=[self.domain]))

        try:
            check_existing_usernames(self.user_specs, self.domain)
        except UserUploadError as e:
            messages.error(request, _(six.text_type(e)))
            return HttpResponseRedirect(reverse(UploadCommCareUsers.urlname, args=[self.domain]))

        try:
            check_duplicate_usernames(self.user_specs)
        except UserUploadError as e:
            messages.error(request, _(six.text_type(e)))
            return HttpResponseRedirect(reverse(UploadCommCareUsers.urlname, args=[self.domain]))

        task_ref = expose_cached_download(payload=None, expiry=1*60*60, file_extension=None)
        task = bulk_upload_async.delay(
            self.domain,
            self.user_specs,
            list(self.group_specs),
        )
        task_ref.set_task(task)
        return HttpResponseRedirect(
            reverse(
                UserUploadStatusView.urlname,
                args=[self.domain, task_ref.download_id]
            )
        )
Esempio n. 9
0
from django.utils.decorators import method_decorator

from django_prbac.utils import has_privilege

from corehq import privileges
from corehq.apps.accounting.decorators import requires_privilege_with_fallback
from corehq.apps.reports.dispatcher import ReportDispatcher, datespan_default
from corehq.apps.users.decorators import require_permission
from corehq.apps.users.models import Permissions
from custom.icds_core.view_utils import check_data_interfaces_blocked_for_domain

require_can_edit_data = require_permission(Permissions.edit_data)

require_form_management_privilege = requires_privilege_with_fallback(
    privileges.DATA_CLEANUP)


class EditDataInterfaceDispatcher(ReportDispatcher):
    prefix = 'edit_data_interface'
    map_name = 'EDIT_DATA_INTERFACES'

    @method_decorator(require_can_edit_data)
    @method_decorator(check_data_interfaces_blocked_for_domain)
    @datespan_default
    def dispatch(self, request, *args, **kwargs):
        from corehq.apps.case_importer.base import ImportCases
        from .interfaces import BulkFormManagementInterface

        if kwargs['report_slug'] == ImportCases.slug:
            return self.bulk_import_case_dispatch(request, *args, **kwargs)
        elif (kwargs['report_slug'] == BulkFormManagementInterface.slug
Esempio n. 10
0
class BaseRepeaterView(BaseAdminProjectSettingsView):
    page_title = ugettext_lazy("Forward Data")
    repeater_form_class = GenericRepeaterForm
    template_name = 'repeaters/add_form_repeater.html'

    @method_decorator(require_permission(Permissions.edit_motech))
    @method_decorator(
        requires_privilege_with_fallback(privileges.DATA_FORWARDING))
    def dispatch(self, request, *args, **kwargs):
        return super(BaseRepeaterView, self).dispatch(request, *args, **kwargs)

    @property
    def page_url(self):
        return reverse(self.urlname, args=[self.domain, self.repeater_type])

    @property
    def parent_pages(self):
        return [{
            'title':
            DomainForwardingOptionsView.page_title,
            'url':
            reverse(DomainForwardingOptionsView.urlname, args=[self.domain]),
        }]

    @property
    def repeater_type(self):
        return self.kwargs['repeater_type']

    @property
    def page_name(self):
        return self.repeater_class.friendly_name

    @property
    @memoized
    def repeater_class(self):
        try:
            return get_all_repeater_types()[self.repeater_type]
        except KeyError:
            raise Http404("No such repeater {}. Valid types: {}".format(
                self.repeater_type, list(get_all_repeater_types())))

    @property
    def add_repeater_form(self):
        return None

    @property
    def page_context(self):
        return {
            'form': self.add_repeater_form,
            'repeater_type': self.repeater_type,
        }

    def initialize_repeater(self):
        raise NotImplementedError

    def make_repeater(self):
        repeater = self.initialize_repeater()
        return self.set_repeater_attr(repeater,
                                      self.add_repeater_form.cleaned_data)

    def set_repeater_attr(self, repeater, cleaned_data):
        repeater.domain = self.domain
        repeater.connection_settings_id = int(
            cleaned_data['connection_settings_id'])
        repeater.format = cleaned_data['format']
        return repeater

    def post_save(self, request, repeater):
        pass

    def post(self, request, *args, **kwargs):
        if self.add_repeater_form.is_valid():
            repeater = self.make_repeater()
            repeater.save()
            return self.post_save(request, repeater)
        return self.get(request, *args, **kwargs)
Esempio n. 11
0
    page_title = gettext_lazy("Create Dashboard Feed")


@location_safe
class CreateNewDailySavedCaseExport(DailySavedExportMixin, CreateNewCustomCaseExportView):
    urlname = 'new_case_daily_saved_export'
    metric_name = 'Daily Saved Case Export'


@location_safe
class CreateNewDailySavedFormExport(DailySavedExportMixin, CreateNewCustomFormExportView):
    urlname = 'new_form_faily_saved_export'
    metric_name = 'Daily Saved Form Export'


@method_decorator(requires_privilege_with_fallback(privileges.ODATA_FEED), name='dispatch')
class CreateODataCaseFeedView(ODataFeedMixin, CreateNewCustomCaseExportView):
    urlname = 'new_odata_case_feed'
    page_title = gettext_lazy("Create OData Case Feed")
    metric_name = 'PowerBI Case Export'

    def create_new_export_instance(self, schema, username, export_settings=None):
        export_instance = super().create_new_export_instance(
            schema,
            username,
            export_settings=export_settings
        )
        clean_odata_columns(export_instance)
        return export_instance

Esempio n. 12
0
class BroadcastListView(BaseMessagingSectionView, DataTablesAJAXPaginationMixin):
    template_name = 'reminders/broadcasts_list.html'
    urlname = 'list_broadcasts'
    page_title = ugettext_lazy('Broadcasts')

    LIST_UPCOMING = 'list_upcoming'
    LIST_PAST = 'list_past'
    DELETE_BROADCAST = 'delete_broadcast'

    @method_decorator(requires_old_reminder_framework())
    @method_decorator(requires_privilege_with_fallback(privileges.OUTBOUND_SMS))
    @use_datatables
    def dispatch(self, *args, **kwargs):
        return super(BroadcastListView, self).dispatch(*args, **kwargs)

    @property
    @memoized
    def project_timezone(self):
        return get_timezone_for_user(None, self.domain)

    def format_recipients(self, broadcast):
        reminders = broadcast.get_reminders()
        if len(reminders) == 0:
            return _('(none)')
        return get_recipient_name(reminders[0].recipient, include_desc=False)

    def format_content(self, broadcast):
        if broadcast.method == METHOD_SMS_SURVEY:
            content = get_form_name(broadcast.events[0].form_unique_id)
        else:
            message = broadcast.events[0].message[broadcast.default_lang]
            if len(message) > 50:
                content = '"%s..."' % message[:47]
            else:
                content = '"%s"' % message
        return content

    def format_broadcast_name(self, broadcast):
        user_time = ServerTime(broadcast.start_datetime).user_time(self.project_timezone)
        return user_time.ui_string(SERVER_DATETIME_FORMAT)

    def format_broadcast_data(self, ids):
        broadcasts = CaseReminderHandler.get_handlers_from_ids(ids)
        result = []
        for broadcast in broadcasts:
            display = self.format_broadcast_name(broadcast)
            result.append([
                display,
                self.format_recipients(broadcast),
                self.format_content(broadcast),
                broadcast._id,
                reverse(EditBroadcastView.urlname, args=[self.domain, broadcast._id]),
                reverse(CopyBroadcastView.urlname, args=[self.domain, broadcast._id]),
            ])
        return result

    def get_broadcast_ajax_response(self, upcoming=True):
        """
        upcoming - True to include only upcoming broadcasts, False to include
                   only past broadcasts.
        """
        if upcoming:
            ids = CaseReminderHandler.get_upcoming_broadcast_ids(self.domain)
        else:
            ids = CaseReminderHandler.get_past_broadcast_ids(self.domain)

        total_records = len(ids)
        ids = ids[self.display_start:self.display_start + self.display_length]
        data = self.format_broadcast_data(ids)
        return self.datatables_ajax_response(data, total_records)

    def delete_broadcast(self, broadcast_id):
        try:
            broadcast = CaseReminderHandler.get(broadcast_id)
        except:
            raise Http404()

        if broadcast.doc_type != 'CaseReminderHandler' or broadcast.domain != self.domain:
            raise Http404()

        broadcast.retire()
        return HttpResponse()

    def get(self, *args, **kwargs):
        action = self.request.GET.get('action')
        if action in (self.LIST_UPCOMING, self.LIST_PAST):
            upcoming = (action == self.LIST_UPCOMING)
            return self.get_broadcast_ajax_response(upcoming)
        else:
            return super(BroadcastListView, self).get(*args, **kwargs)

    def post(self, *args, **kwargs):
        action = self.request.POST.get('action')
        if action == self.DELETE_BROADCAST:
            return self.delete_broadcast(self.request.POST.get('broadcast_id', None))
        else:
            return HttpResponse(status=400)
Esempio n. 13
0
class ScheduledRemindersCalendarView(BaseMessagingSectionView):
    urlname = 'scheduled_reminders'
    page_title = ugettext_noop("Reminder Calendar")
    template_name = 'reminders/partial/scheduled_reminders.html'

    @method_decorator(requires_old_reminder_framework())
    @method_decorator(requires_privilege_with_fallback(privileges.OUTBOUND_SMS))
    @method_decorator(reminders_framework_permission)
    def dispatch(self, *args, **kwargs):
        return super(BaseMessagingSectionView, self).dispatch(*args, **kwargs)

    @property
    def page_context(self):
        page_context = super(ScheduledRemindersCalendarView, self).page_context
        timezone = Domain.get_by_name(self.domain).get_default_timezone()
        reminders = CaseReminderHandler.get_all_reminders(self.domain)
        dates = []
        now = datetime.utcnow()
        timezone_now = datetime.now(timezone)
        today = timezone_now.date()

        def adjust_next_fire_to_timezone(reminder_utc):
            return ServerTime(reminder_utc.next_fire).user_time(timezone).done().replace(tzinfo=None)

        if reminders:
            start_date = adjust_next_fire_to_timezone(reminders[0]).date()
            if today < start_date:
                start_date = today
            end_date = adjust_next_fire_to_timezone(reminders[-1]).date()
        else:
            start_date = end_date = today
        # make sure start date is a Monday and enddate is a Sunday
        start_date -= timedelta(days=start_date.weekday())
        end_date += timedelta(days=6 - end_date.weekday())
        while start_date <= end_date:
            dates.append(start_date)
            start_date += timedelta(days=1)

        reminder_data = []
        for reminder in reminders:
            handler = reminder.handler
            recipient = reminder.recipient
            recipient_desc = get_recipient_name(recipient)
            case = reminder.case

            reminder_data.append({
                "handler_name": handler.nickname,
                "next_fire": adjust_next_fire_to_timezone(reminder),
                "recipient_desc": recipient_desc,
                "recipient_type": handler.recipient,
                "case_id": case.case_id if case is not None else None,
                "case_name": case.name if case is not None else None,
            })

        page_context.update({
            'domain': self.domain,
            'reminder_data': reminder_data,
            'dates': dates,
            'today': today,
            'now': now,
            'timezone': timezone,
            'timezone_now': timezone_now,
        })
        return page_context
Esempio n. 14
0
class RemindersListView(BaseMessagingSectionView):
    template_name = 'reminders/reminders_list.html'
    urlname = "list_reminders_new"
    page_title = ugettext_noop("Reminder Definitions")

    @method_decorator(requires_old_reminder_framework())
    @method_decorator(requires_privilege_with_fallback(privileges.OUTBOUND_SMS))
    @use_datatables
    def dispatch(self, *args, **kwargs):
        return super(BaseMessagingSectionView, self).dispatch(*args, **kwargs)

    @property
    def page_url(self):
        return reverse(self.urlname, args=[self.domain])

    @property
    def can_use_survey(self):
        return can_use_survey_reminders(self.request)

    @property
    def reminders(self):
        all_handlers = CaseReminderHandler.get_handlers(self.domain,
            reminder_type_filter=REMINDER_TYPE_DEFAULT)
        if not self.can_use_survey:
            all_handlers = [x for x in all_handlers if x.method not in [METHOD_IVR_SURVEY, METHOD_SMS_SURVEY]]
        for handler in all_handlers:
            yield self._fmt_reminder_data(handler)

    @property
    def page_context(self):
        return {
            'reminders': list(self.reminders),
        }

    @property
    def reminder_id(self):
        return self.request.POST['reminderId']

    @property
    @memoized
    def reminder(self):
        return CaseReminderHandler.get(self.reminder_id)

    def _fmt_reminder_data(self, reminder):
        return {
            'id': reminder._id,
            'isActive': reminder.active,
            'caseType': reminder.case_type,
            'name': reminder.nickname,
            'url': reverse(EditScheduledReminderView.urlname, args=[self.domain, reminder._id]),
        }

    def get_action_response(self, action):
        try:
            assert self.reminder.domain == self.domain
            assert self.reminder.doc_type == "CaseReminderHandler"
            if self.reminder.locked:
                return {
                    'success': False,
                    'locked': True,
                }

            if action in [ACTION_ACTIVATE, ACTION_DEACTIVATE]:
                self.reminder.active = (action == ACTION_ACTIVATE)
                self.reminder.save()
            elif action == ACTION_DELETE:
                self.reminder.retire()
            return {
                'success': True,
            }
        except Exception as e:
            msg = ("Couldn't process action '%s' for reminder definition"
                % action)
            notify_exception(None, message=msg, details={
                'domain': self.domain,
                'handler_id': self.reminder_id,
            })
            return {
                'success': False,
            }

    def post(self, *args, **kwargs):
        action = self.request.POST.get('action')
        if action in [ACTION_ACTIVATE, ACTION_DEACTIVATE, ACTION_DELETE]:
            return HttpResponse(json.dumps(self.get_action_response(action)))
        return HttpResponse(status=400)
Esempio n. 15
0
from corehq.apps.users.decorators import require_permission
from corehq.apps.users.models import CommCareUser, Permissions
from corehq.apps.users.util import normalize_username
from couchexport.export import UnsupportedExportFormat, export_raw
from couchexport.models import Format
from couchexport.shortcuts import export_response
from dimagi.utils.couch.bulk import CouchTransaction
from dimagi.utils.excel import WorkbookJSONReader, WorksheetNotFound
from dimagi.utils.logging import notify_exception
from dimagi.utils.web import json_response
from dimagi.utils.decorators.view import get_file


require_can_edit_fixtures = lambda *args, **kwargs: (
    require_permission(Permissions.edit_data)(
        requires_privilege_with_fallback(privileges.LOOKUP_TABLES)(*args, **kwargs)
    )
)


def strip_json(obj, disallow_basic=None, disallow=None):
    disallow = disallow or []
    if disallow_basic is None:
        disallow_basic = ['_rev', 'domain', 'doc_type']
    disallow += disallow_basic
    ret = {}
    try:
        obj = obj.to_json()
    except Exception:
        pass
    for key in obj:
Esempio n. 16
0
class KeywordsListView(BaseMessagingSectionView, CRUDPaginatedViewMixin):
    template_name = 'reminders/keyword_list.html'
    urlname = 'keyword_list'
    page_title = ugettext_noop("Keywords")

    limit_text = ugettext_noop("keywords per page")
    empty_notification = ugettext_noop("You have no keywords. Please add one!")
    loading_message = ugettext_noop("Loading keywords...")

    @use_multiselect
    @method_decorator(requires_privilege_with_fallback(privileges.INBOUND_SMS))
    def dispatch(self, *args, **kwargs):
        return super(KeywordsListView, self).dispatch(*args, **kwargs)

    @property
    def page_url(self):
        return reverse(self.urlname, args=[self.domain])

    @property
    @memoized
    def total(self):
        return Keyword.get_by_domain(self.domain).count()

    @property
    def column_names(self):
        return [
            _("Keyword"),
            _("Description"),
            _("Action"),
        ]

    @property
    def page_context(self):
        context = self.pagination_context
        context['linked_domains'] = [
            domain_link.linked_domain
            for domain_link in get_linked_domains(self.domain)
        ]
        context['linkable_keywords'] = self._linkable_keywords()
        return context

    def _linkable_keywords(self):
        LinkableKeyword = namedtuple('LinkableKeyword',
                                     'keyword can_be_linked')
        linkable_keywords = []
        for keyword in self._all_keywords():
            sends_to_usergroup = keyword.keywordaction_set.filter(
                recipient=KeywordAction.RECIPIENT_USER_GROUP).count()
            if sends_to_usergroup:
                linkable_keywords.append(LinkableKeyword(keyword, False))
            else:
                linkable_keywords.append(LinkableKeyword(keyword, True))
        return linkable_keywords

    @memoized
    def _all_keywords(self):
        return Keyword.get_by_domain(self.domain)

    @property
    def paginated_list(self):
        for keyword in self._all_keywords()[self.skip:self.skip + self.limit]:
            yield {
                'itemData': self._fmt_keyword_data(keyword),
                'template': 'keyword-row-template',
            }

    def _fmt_keyword_data(self, keyword):
        return {
            'id':
            keyword.couch_id,
            'keyword':
            keyword.keyword,
            'description':
            keyword.description,
            'editUrl':
            reverse(EditStructuredKeywordView.urlname,
                    args=[self.domain, keyword.couch_id])
            if keyword.is_structured_sms() else reverse(
                EditNormalKeywordView.urlname,
                args=[self.domain, keyword.couch_id]),
            'deleteModalId':
            'delete-%s' % keyword.couch_id,
        }

    def _fmt_deleted_keyword_data(self, keyword):
        return {
            'keyword': keyword.keyword,
            'description': keyword.description,
        }

    def get_deleted_item_data(self, item_id):
        try:
            k = Keyword.objects.get(couch_id=item_id)
        except Keyword.DoesNotExist:
            raise Http404()

        if k.domain != self.domain:
            raise Http404()

        k.delete()

        return {
            'itemData': self._fmt_deleted_keyword_data(k),
            'template': 'keyword-deleted-template',
        }

    def post(self, *args, **kwargs):
        return self.paginate_crud_response
Esempio n. 17
0
class CreateBroadcastView(BaseMessagingSectionView):
    urlname = 'add_broadcast'
    page_title = ugettext_lazy('New Broadcast')
    template_name = 'reminders/broadcast.html'
    force_create_new_broadcast = False

    @method_decorator(requires_old_reminder_framework())
    @method_decorator(requires_privilege_with_fallback(privileges.OUTBOUND_SMS))
    @use_jquery_ui
    @use_timepicker
    @use_select2
    def dispatch(self, *args, **kwargs):
        return super(BaseMessagingSectionView, self).dispatch(*args, **kwargs)

    @property
    @memoized
    def project_timezone(self):
        return get_timezone_for_user(None, self.domain)

    @property
    def parent_pages(self):
        return [
            {
                'title': BroadcastListView.page_title,
                'url': reverse(BroadcastListView.urlname, args=[self.domain]),
            },
        ]

    def create_new_broadcast(self):
        return CaseReminderHandler(
            domain=self.domain,
            nickname='One-time Reminder',
            reminder_type=REMINDER_TYPE_ONE_TIME,
        )

    @property
    @memoized
    def broadcast(self):
        return self.create_new_broadcast()

    @property
    def form_kwargs(self):
        return {
            'domain': self.domain,
            'can_use_survey': can_use_survey_reminders(self.request),
        }

    @property
    def form_class(self):
        if toggles.EWS_BROADCAST_BY_ROLE.enabled(self.domain):
            return EWSBroadcastForm
        else:
            return BroadcastForm

    @property
    @memoized
    def broadcast_form(self):
        if self.request.method == 'POST':
            return self.form_class(self.request.POST, **self.form_kwargs)
        else:
            return self.form_class(**self.form_kwargs)

    @property
    def page_context(self):
        return {
            'form': self.broadcast_form,
        }

    def save_model(self, broadcast, form):
        broadcast.default_lang = 'xx'
        broadcast.method = form.cleaned_data.get('content_type')
        broadcast.recipient = form.cleaned_data.get('recipient_type')
        broadcast.start_condition_type = ON_DATETIME
        broadcast.start_datetime = form.cleaned_data.get('datetime')
        broadcast.start_offset = 0
        broadcast.events = [CaseReminderEvent(
            day_num=0,
            fire_time=time(0, 0),
            form_unique_id=form.cleaned_data.get('form_unique_id'),
            message=({broadcast.default_lang: form.cleaned_data.get('message')}
                     if form.cleaned_data.get('message') else {}),
            subject=({broadcast.default_lang: form.cleaned_data.get('subject')}
                     if form.cleaned_data.get('subject') else {}),
            callback_timeout_intervals=[],
        )]
        broadcast.schedule_length = 1
        broadcast.event_interpretation = EVENT_AS_OFFSET
        broadcast.max_iteration_count = 1
        broadcast.sample_id = form.cleaned_data.get('case_group_id')
        broadcast.user_group_id = form.cleaned_data.get('user_group_id')
        broadcast.location_ids = form.cleaned_data.get('location_ids')
        broadcast.include_child_locations = form.cleaned_data.get('include_child_locations')
        if toggles.EWS_BROADCAST_BY_ROLE.enabled(self.domain):
            broadcast.user_data_filter = form.get_user_data_filter()
        broadcast.save()

    def post(self, request, *args, **kwargs):
        if self.broadcast_form.is_valid():
            if self.force_create_new_broadcast:
                broadcast = self.create_new_broadcast()
            else:
                broadcast = self.broadcast

            self.save_model(broadcast, self.broadcast_form)
            return HttpResponseRedirect(reverse(BroadcastListView.urlname, args=[self.domain]))
        return self.get(request, *args, **kwargs)
Esempio n. 18
0
from corehq.const import SERVER_DATETIME_FORMAT
from corehq.util.timezones.conversions import ServerTime
from corehq.util.timezones.utils import get_timezone_for_user
from custom.ewsghana.forms import EWSBroadcastForm

from dimagi.utils.couch.cache.cache_core import get_redis_client
from memoized import memoized
from dimagi.utils.logging import notify_exception

ACTION_ACTIVATE = 'activate'
ACTION_DEACTIVATE = 'deactivate'
ACTION_DELETE = 'delete'

survey_reminders_permission = lambda *args, **kwargs: (
    require_permission(Permissions.edit_data)
    (requires_privilege_with_fallback(privileges.INBOUND_SMS)
     (*args, **kwargs)))


def add_migration_in_progress_message(request):
    messages.warning(
        request,
        _("Maintenance is underway to upgrade your experience with "
          "messaging reminders. As a result, pages which create or "
          "edit reminders are currently unavailable. Please check "
          "back soon."))


def get_project_time_info(domain):
    timezone = get_timezone_for_user(None, domain)
    now = pytz.utc.localize(datetime.utcnow())
    timezone_now = now.astimezone(timezone)
Esempio n. 19
0
class ConditionalAlertListView(BaseMessagingSectionView,
                               DataTablesAJAXPaginationMixin):
    template_name = 'scheduling/conditional_alert_list.html'
    urlname = 'conditional_alert_list'
    page_title = ugettext_lazy('Schedule a Conditional Message')

    LIST_CONDITIONAL_ALERTS = 'list_conditional_alerts'
    ACTION_ACTIVATE = 'activate'
    ACTION_DEACTIVATE = 'deactivate'
    ACTION_DELETE = 'delete'

    @method_decorator(_requires_new_reminder_framework())
    @method_decorator(requires_privilege_with_fallback(privileges.OUTBOUND_SMS)
                      )
    @method_decorator(require_permission(Permissions.edit_data))
    @use_datatables
    def dispatch(self, *args, **kwargs):
        return super(ConditionalAlertListView, self).dispatch(*args, **kwargs)

    def get_conditional_alerts_queryset(self):
        return (AutomaticUpdateRule.objects.filter(
            domain=self.domain,
            workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING,
            deleted=False).order_by('case_type', 'name', 'id'))

    def get_conditional_alerts_ajax_response(self):
        query = self.get_conditional_alerts_queryset()
        total_records = query.count()

        rules = query[self.display_start:self.display_start +
                      self.display_length]
        data = []
        for rule in rules:
            data.append([
                '< delete placeholder >',
                rule.name,
                rule.case_type,
                rule.get_messaging_rule_schedule().active,
                '< action placeholder >',
                rule.locked_for_editing,
                MessagingRuleProgressHelper(rule.pk).get_progress_pct(),
                rule.pk,
            ])

        return self.datatables_ajax_response(data, total_records)

    def get(self, request, *args, **kwargs):
        action = request.GET.get('action')
        if action == self.LIST_CONDITIONAL_ALERTS:
            return self.get_conditional_alerts_ajax_response()

        return super(ConditionalAlertListView, self).get(*args, **kwargs)

    def get_rule(self, rule_id):
        try:
            return AutomaticUpdateRule.objects.get(
                domain=self.domain,
                pk=rule_id,
                deleted=False,
                workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING)
        except AutomaticUpdateRule.DoesNotExist:
            raise Http404()

    def get_activate_ajax_response(self, active_flag, rule):
        """
        When we deactivate a conditional alert from the UI, we are only
        deactivating the schedule that sends the content. The rule itself
        stays active.
        This is because we want to be keeping all the schedule instances
        up to date (though inactive), so that if the schedule is reactivated,
        we don't send a large quantity of stale messages.
        """
        with transaction.atomic():
            schedule = rule.get_messaging_rule_schedule()
            schedule.active = active_flag
            schedule.save()
            initiate_messaging_rule_run(self.domain, rule.pk)

        return HttpResponse()

    def get_delete_ajax_response(self, rule):
        rule.soft_delete()
        return HttpResponse()

    def post(self, request, *args, **kwargs):
        action = request.POST.get('action')
        rule_id = request.POST.get('rule_id')

        with get_conditional_alert_edit_critical_section(rule_id):
            rule = self.get_rule(rule_id)
            if rule.locked_for_editing:
                return HttpResponseBadRequest()

            if action == self.ACTION_ACTIVATE:
                return self.get_activate_ajax_response(True, rule)
            elif action == self.ACTION_DEACTIVATE:
                return self.get_activate_ajax_response(False, rule)
            elif action == self.ACTION_DELETE:
                return self.get_delete_ajax_response(rule)
            else:
                return HttpResponseBadRequest()
Esempio n. 20
0
        form_query_string_pending = _change_record_state(
            self.request.GET, 'PENDING').urlencode()

        context.update(
            total=total,
            total_pending=total_pending,
            total_cancelled=total_cancelled,
            form_query_string=form_query_string,
            form_query_string_pending=form_query_string_pending,
            form_query_string_cancelled=form_query_string_cancelled,
        )
        return context


@method_decorator(domain_admin_required, name='dispatch')
@method_decorator(requires_privilege_with_fallback(privileges.DATA_FORWARDING),
                  name='dispatch')
class RepeatRecordView(View):
    urlname = 'repeat_record'
    http_method_names = ['get', 'post']

    @staticmethod
    def get_record_or_404(domain, record_id):
        try:
            record = RepeatRecord.get(record_id)
        except ResourceNotFound:
            raise Http404()

        if record.domain != domain:
            raise Http404()
Esempio n. 21
0
class BroadcastListView(BaseMessagingSectionView,
                        DataTablesAJAXPaginationMixin):
    template_name = 'scheduling/broadcasts_list.html'
    urlname = 'new_list_broadcasts'
    page_title = ugettext_lazy('Schedule a Message')

    LIST_SCHEDULED = 'list_scheduled'
    LIST_IMMEDIATE = 'list_immediate'
    ACTION_ACTIVATE_SCHEDULED_BROADCAST = 'activate_scheduled_broadcast'
    ACTION_DEACTIVATE_SCHEDULED_BROADCAST = 'deactivate_scheduled_broadcast'
    ACTION_DELETE_SCHEDULED_BROADCAST = 'delete_scheduled_broadcast'

    @method_decorator(_requires_new_reminder_framework())
    @method_decorator(requires_privilege_with_fallback(privileges.OUTBOUND_SMS)
                      )
    @method_decorator(require_permission(Permissions.edit_data))
    @use_datatables
    def dispatch(self, *args, **kwargs):
        return super(BroadcastListView, self).dispatch(*args, **kwargs)

    @cached_property
    def project_timezone(self):
        return get_timezone_for_user(None, self.domain)

    def _format_time(self, time):
        if not time:
            return ''

        user_time = ServerTime(time).user_time(self.project_timezone)
        return user_time.ui_string(SERVER_DATETIME_FORMAT)

    def get_scheduled_ajax_response(self):
        query = (ScheduledBroadcast.objects.filter(domain=self.domain,
                                                   deleted=False).order_by(
                                                       '-last_sent_timestamp',
                                                       'id'))
        total_records = query.count()
        query = query.select_related('schedule')
        broadcasts = query[self.display_start:self.display_start +
                           self.display_length]

        data = []
        for broadcast in broadcasts:
            data.append([
                '< delete placeholder >',
                broadcast.name,
                self._format_time(broadcast.last_sent_timestamp),
                broadcast.schedule.active,
                '< action placeholder >',
                broadcast.id,
            ])
        return self.datatables_ajax_response(data, total_records)

    def get_immediate_ajax_response(self):
        query = (ImmediateBroadcast.objects.filter(domain=self.domain,
                                                   deleted=False).order_by(
                                                       '-last_sent_timestamp',
                                                       'id'))
        total_records = query.count()
        broadcasts = query[self.display_start:self.display_start +
                           self.display_length]

        data = []
        for broadcast in broadcasts:
            data.append([
                broadcast.name,
                self._format_time(broadcast.last_sent_timestamp),
                broadcast.id,
            ])
        return self.datatables_ajax_response(data, total_records)

    def get_scheduled_broadcast(self, broadcast_id):
        try:
            return ScheduledBroadcast.objects.get(domain=self.domain,
                                                  pk=broadcast_id,
                                                  deleted=False)
        except ScheduledBroadcast.DoesNotExist:
            raise Http404()

    def get_scheduled_broadcast_activate_ajax_response(self, active_flag,
                                                       broadcast_id):
        broadcast = self.get_scheduled_broadcast(broadcast_id)
        TimedSchedule.objects.filter(schedule_id=broadcast.schedule_id).update(
            active=active_flag)
        refresh_timed_schedule_instances.delay(broadcast.schedule_id,
                                               broadcast.recipients,
                                               start_date=broadcast.start_date)

        return HttpResponse()

    def get_scheduled_broadcast_delete_ajax_response(self, broadcast_id):
        broadcast = self.get_scheduled_broadcast(broadcast_id)
        broadcast.soft_delete()
        return HttpResponse()

    def get(self, request, *args, **kwargs):
        action = request.GET.get('action')
        if action == self.LIST_SCHEDULED:
            return self.get_scheduled_ajax_response()
        elif action == self.LIST_IMMEDIATE:
            return self.get_immediate_ajax_response()

        return super(BroadcastListView, self).get(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        action = request.POST.get('action')
        broadcast_id = request.POST.get('broadcast_id')

        with get_broadcast_edit_critical_section(
                EditScheduleView.SCHEDULED_BROADCAST, broadcast_id):
            if action == self.ACTION_ACTIVATE_SCHEDULED_BROADCAST:
                return self.get_scheduled_broadcast_activate_ajax_response(
                    True, broadcast_id)
            elif action == self.ACTION_DEACTIVATE_SCHEDULED_BROADCAST:
                return self.get_scheduled_broadcast_activate_ajax_response(
                    False, broadcast_id)
            elif action == self.ACTION_DELETE_SCHEDULED_BROADCAST:
                return self.get_scheduled_broadcast_delete_ajax_response(
                    broadcast_id)
            else:
                return HttpResponseBadRequest()
Esempio n. 22
0
from corehq import privileges
from corehq.apps.accounting.decorators import requires_privilege_with_fallback
from corehq.apps.reports.dispatcher import ProjectReportDispatcher
from django.utils.decorators import method_decorator
from corehq.apps.users.decorators import require_permission
from corehq.apps.users.models import Permissions

require_can_edit_fixtures = lambda *args, **kwargs: (
    require_permission(Permissions.edit_data)
    (requires_privilege_with_fallback(privileges.LOOKUP_TABLES)
     (*args, **kwargs)))


class FixtureInterfaceDispatcher(ProjectReportDispatcher):
    prefix = 'fixture_interface'
    map_name = 'FIXTURE_INTERFACES'

    @method_decorator(require_can_edit_fixtures)
    def dispatch(self, request, *args, **kwargs):
        return super(FixtureInterfaceDispatcher,
                     self).dispatch(request, *args, **kwargs)
Esempio n. 23
0
class UploadCommCareUsers(BaseManageCommCareUserView):
    template_name = 'users/upload_commcare_users.html'
    urlname = 'upload_commcare_users'
    page_title = ugettext_noop("Bulk Upload Mobile Workers")

    @method_decorator(requires_privilege_with_fallback(privileges.BULK_USER_MANAGEMENT))
    def dispatch(self, request, *args, **kwargs):
        return super(UploadCommCareUsers, self).dispatch(request, *args, **kwargs)

    @property
    def page_context(self):
        request_params = self.request.GET if self.request.method == 'GET' else self.request.POST
        context = {
            'bulk_upload': {
                "help_site": {
                    "address": BULK_MOBILE_HELP_SITE,
                    "name": _("CommCare Help Site"),
                },
                "download_url": reverse(
                    "download_commcare_users", args=(self.domain,)),
                "adjective": _("mobile worker"),
                "plural_noun": _("mobile workers"),
            },
            'show_secret_settings': request_params.get("secret", False),
        }
        context.update({
            'bulk_upload_form': get_bulk_upload_form(context),
        })
        return context

    def post(self, request, *args, **kwargs):
        """View's dispatch method automatically calls this"""
        upload = request.FILES.get('bulk_upload_file')
        try:
            self.workbook = WorkbookJSONReader(upload)
        except InvalidExcelFileException:
            try:
                csv.DictReader(io.StringIO(upload.read().decode('ascii'),
                                           newline=None))
                return HttpResponseBadRequest(
                    "CommCare HQ no longer supports CSV upload. "
                    "Please convert to Excel 2007 or higher (.xlsx) "
                    "and try again."
                )
            except UnicodeDecodeError:
                return HttpResponseBadRequest("Unrecognized format")
        except JSONReaderError as e:
            messages.error(request,
                           'Your upload was unsuccessful. %s' % e.message)
            return self.get(request, *args, **kwargs)
        except HeaderValueError as e:
            return HttpResponseBadRequest("Upload encountered a data type error: %s"
                                          % e.message)

        try:
            self.user_specs = self.workbook.get_worksheet(title='users')
        except WorksheetNotFound:
            try:
                self.user_specs = self.workbook.get_worksheet()
            except WorksheetNotFound:
                return HttpResponseBadRequest("Workbook has no worksheets")

        try:
            self.group_specs = self.workbook.get_worksheet(title='groups')
        except WorksheetNotFound:
            self.group_specs = []

        try:
            check_headers(self.user_specs)
        except UserUploadError as e:
            messages.error(request, _(e.message))
            return HttpResponseRedirect(reverse(UploadCommCareUsers.urlname, args=[self.domain]))

        # convert to list here because iterator destroys the row once it has
        # been read the first time
        self.user_specs = list(self.user_specs)

        for user_spec in self.user_specs:
            try:
                user_spec['username'] = enforce_string_type(user_spec['username'])
            except StringTypeRequiredError:
                messages.error(
                    request,
                    _("Error: Expected username to be a Text type for username {0}")
                    .format(user_spec['username'])
                )
                return HttpResponseRedirect(reverse(UploadCommCareUsers.urlname, args=[self.domain]))

        try:
            check_existing_usernames(self.user_specs, self.domain)
        except UserUploadError as e:
            messages.error(request, _(e.message))
            return HttpResponseRedirect(reverse(UploadCommCareUsers.urlname, args=[self.domain]))

        try:
            check_duplicate_usernames(self.user_specs)
        except UserUploadError as e:
            messages.error(request, _(e.message))
            return HttpResponseRedirect(reverse(UploadCommCareUsers.urlname, args=[self.domain]))

        task_ref = expose_cached_download(payload=None, expiry=1*60*60, file_extension=None)
        task = bulk_upload_async.delay(
            self.domain,
            self.user_specs,
            list(self.group_specs),
        )
        task_ref.set_task(task)
        return HttpResponseRedirect(
            reverse(
                UserUploadStatusView.urlname,
                args=[self.domain, task_ref.download_id]
            )
        )
Esempio n. 24
0
    page_title = ugettext_lazy("Create Dashboard Feed")


@location_safe
class CreateNewDailySavedCaseExport(DailySavedExportMixin,
                                    CreateNewCustomCaseExportView):
    urlname = 'new_case_daily_saved_export'


@location_safe
class CreateNewDailySavedFormExport(DailySavedExportMixin,
                                    CreateNewCustomFormExportView):
    urlname = 'new_form_faily_saved_export'


@method_decorator(requires_privilege_with_fallback(privileges.ODATA_FEED),
                  name='dispatch')
class CreateODataCaseFeedView(ODataFeedMixin, CreateNewCustomCaseExportView):
    urlname = 'new_odata_case_feed'
    page_title = ugettext_lazy("Create OData Case Feed")

    def create_new_export_instance(self, schema):
        export_instance = super(CreateODataCaseFeedView,
                                self).create_new_export_instance(schema)
        clean_odata_columns(export_instance)
        return export_instance


@method_decorator(requires_privilege_with_fallback(privileges.ODATA_FEED),
                  name='dispatch')
class CreateODataFormFeedView(ODataFeedMixin, CreateNewCustomFormExportView):
Esempio n. 25
0
class CreateScheduleView(BaseMessagingSectionView, AsyncHandlerMixin):
    urlname = 'create_schedule'
    page_title = ugettext_lazy('New Broadcast')
    template_name = 'scheduling/create_schedule.html'
    async_handlers = [MessagingRecipientHandler]
    read_only_mode = False

    @method_decorator(
        requires_privilege_with_fallback(privileges.REMINDERS_FRAMEWORK))
    @use_jquery_ui
    @use_timepicker
    def dispatch(self, *args, **kwargs):
        return super(CreateScheduleView, self).dispatch(*args, **kwargs)

    @property
    def parent_pages(self):
        return [
            {
                'title': BroadcastListView.page_title,
                'url': reverse(BroadcastListView.urlname, args=[self.domain]),
            },
        ]

    @property
    def broadcast(self):
        return None

    @property
    def schedule(self):
        return None

    @cached_property
    def schedule_form(self):
        args = [
            self.domain, self.schedule, self.can_use_inbound_sms,
            self.broadcast
        ]

        if self.request.method == 'POST':
            args.append(self.request.POST)

        return BroadcastForm(*args, is_system_admin=self.is_system_admin)

    @property
    def page_context(self):
        context = super().page_context
        context.update({
            'schedule_form': self.schedule_form,
            'read_only_mode': self.read_only_mode,
        })
        return context

    def post(self, request, *args, **kwargs):
        if self.async_response is not None:
            return self.async_response

        if self.schedule_form.is_valid():
            if not self.can_use_inbound_sms and self.schedule_form.uses_sms_survey:
                return HttpResponseBadRequest(
                    "Cannot create or edit survey reminders because subscription "
                    "does not have access to inbound SMS")

            broadcast, schedule = self.schedule_form.save_broadcast_and_schedule(
            )
            if isinstance(schedule, AlertSchedule):
                refresh_alert_schedule_instances.delay(schedule.schedule_id,
                                                       broadcast.recipients)
            elif isinstance(schedule, TimedSchedule):
                refresh_timed_schedule_instances.delay(
                    schedule.schedule_id,
                    broadcast.recipients,
                    start_date=broadcast.start_date)
            else:
                raise TypeError("Expected AlertSchedule or TimedSchedule")

            return HttpResponseRedirect(
                reverse(BroadcastListView.urlname, args=[self.domain]))

        return self.get(request, *args, **kwargs)
Esempio n. 26
0
class ArchiveFormView(DataInterfaceSection):
    template_name = 'data_interfaces/interfaces/import_forms.html'
    urlname = 'archive_forms'
    page_title = ugettext_noop("Bulk Archive Forms")

    ONE_MB = 1000000
    MAX_SIZE = 3 * ONE_MB

    @method_decorator(
        requires_privilege_with_fallback(privileges.BULK_CASE_MANAGEMENT))
    def dispatch(self, request, *args, **kwargs):
        if not toggles.BULK_ARCHIVE_FORMS.enabled(request.user.username):
            raise Http404()
        return super(ArchiveFormView, self).dispatch(request, *args, **kwargs)

    @property
    def page_url(self):
        return reverse(self.urlname, args=[self.domain])

    @property
    def page_context(self):
        context = {}
        context.update({
            'bulk_upload': {
                "download_url":
                static('data_interfaces/xlsx/forms_bulk_example.xlsx'),
                "adjective":
                _("example"),
                "verb":
                _("archive"),
                "plural_noun":
                _("forms"),
            },
        })
        context.update({
            'bulk_upload_form': get_bulk_upload_form(context),
        })
        return context

    @property
    @memoized
    def uploaded_file(self):
        try:
            bulk_file = self.request.FILES['bulk_upload_file']
            if bulk_file.size > self.MAX_SIZE:
                raise BulkUploadCasesException(
                    _(u"File size too large. "
                      "Please upload file less than"
                      " {size} Megabytes").format(size=self.MAX_SIZE /
                                                  self.ONE_MB))

        except KeyError:
            raise BulkUploadCasesException(_("No files uploaded"))
        try:
            return WorkbookJSONReader(bulk_file)
        except InvalidExcelFileException:
            try:
                csv.DictReader(
                    io.StringIO(bulk_file.read().decode('utf-8'),
                                newline=None))
                raise BulkUploadCasesException(
                    _("CommCare HQ does not support that file type."
                      "Please convert to Excel 2007 or higher (.xlsx) "
                      "and try again."))
            except UnicodeDecodeError:
                raise BulkUploadCasesException(_("Unrecognized format"))
        except JSONReaderError as e:
            raise BulkUploadCasesException(
                _('Your upload was unsuccessful. %s') % e.message)

    def process(self):
        try:
            bulk_archive_forms.delay(self.domain, self.request.couch_user,
                                     list(self.uploaded_file.get_worksheet()))
            messages.success(
                self.request,
                _("We received your file and are processing it. "
                  "You will receive an email when it has finished."))
        except BulkUploadCasesException as e:
            messages.error(self.request, e.message)
        return None

    def post(self, request, *args, **kwargs):
        self.process()
        return HttpResponseRedirect(self.page_url)
Esempio n. 27
0
class AutomaticUpdateRuleListView(DataInterfaceSection, CRUDPaginatedViewMixin):
    template_name = 'data_interfaces/list_automatic_update_rules.html'
    urlname = 'automatic_update_rule_list'
    page_title = ugettext_lazy("Automatically Close Cases")

    limit_text = ugettext_lazy("rules per page")
    empty_notification = ugettext_lazy("You have no case rules.")
    loading_message = ugettext_lazy("Loading rules...")
    deleted_items_header = ugettext_lazy("Deleted Rules")

    ACTION_ACTIVATE = 'activate'
    ACTION_DEACTIVATE = 'deactivate'

    @method_decorator(requires_privilege_with_fallback(privileges.DATA_CLEANUP))
    def dispatch(self, *args, **kwargs):
        return super(AutomaticUpdateRuleListView, self).dispatch(*args, **kwargs)

    @property
    def parameters(self):
        return self.request.POST if self.request.method == 'POST' else self.request.GET

    @property
    def allowed_actions(self):
        actions = super(AutomaticUpdateRuleListView, self).allowed_actions
        actions.append(self.ACTION_ACTIVATE)
        actions.append(self.ACTION_DEACTIVATE)
        return actions

    @property
    def page_context(self):
        context = self.pagination_context
        context['help_site_url'] = 'https://confluence.dimagi.com/display/commcarepublic/Automatically+Close+Cases'
        return context

    @property
    def total(self):
        return self._rules().count()

    @property
    def column_names(self):
        return [
            _("Name"),
            _("Case Type"),
            _("Status"),
            _("Last Run"),
            _("Action"),
        ]

    @property
    @memoized
    def project_timezone(self):
        return get_timezone_for_user(None, self.domain)

    def _format_rule(self, rule):
        return {
            'id': rule.pk,
            'name': rule.name,
            'case_type': rule.case_type,
            'active': rule.active,
            'last_run': (ServerTime(rule.last_run)
                         .user_time(self.project_timezone)
                         .done()
                         .strftime(SERVER_DATETIME_FORMAT)) if rule.last_run else '-',
            'edit_url': reverse(EditCaseRuleView.urlname, args=[self.domain, rule.pk]),
            'action_error': "",     # must be provided because knockout template looks for it
        }

    @property
    def paginated_list(self):
        for rule in self._rules()[self.skip:self.skip + self.limit]:
            yield {
                'itemData': self._format_rule(rule),
                'template': 'base-rule-template',
            }

    @memoized
    def _rules(self):
        return AutomaticUpdateRule.by_domain(
            self.domain,
            AutomaticUpdateRule.WORKFLOW_CASE_UPDATE,
            active_only=False,
        ).order_by('name', 'id')

    def post(self, *args, **kwargs):
        return self.paginate_crud_response

    def _get_rule(self, rule_id):
        if rule_id is None:
            return None, _("Please provide an id.")

        try:
            rule = AutomaticUpdateRule.objects.get(pk=rule_id, workflow=AutomaticUpdateRule.WORKFLOW_CASE_UPDATE)
        except AutomaticUpdateRule.DoesNotExist:
            return None, _("Rule not found.")

        if rule.domain != self.domain:
            return None, _("Rule not found.")

        return rule, None

    def get_deleted_item_data(self, rule_id):
        (rule, error) = self._get_rule(rule_id)
        if rule is None:
            return {'success': False, 'error': error}

        rule.soft_delete()

        return {
            'itemData': {
                'name': rule.name,
            },
            'template': 'rule-deleted-template',
        }

    def update_rule(self):
        (rule, error) = self._get_rule(self.parameters.get('id'))
        if rule is None:
            return {'success': False, 'error': error}

        if self.action == self.ACTION_ACTIVATE:
            rule.activate()
        elif self.action == self.ACTION_DEACTIVATE:
            rule.activate(False)

        return {'success': True, 'itemData': self._format_rule(rule)}

    @property
    def activate_response(self):
        return self.update_rule()

    @property
    def deactivate_response(self):
        return self.update_rule()
Esempio n. 28
0
class AutomaticUpdateRuleListView(JSONResponseMixin, DataInterfaceSection):
    template_name = 'data_interfaces/list_automatic_update_rules.html'
    urlname = 'automatic_update_rule_list'
    page_title = ugettext_lazy("Automatically Close Cases")

    ACTION_ACTIVATE = 'activate'
    ACTION_DEACTIVATE = 'deactivate'
    ACTION_DELETE = 'delete'

    @property
    @memoized
    def project_timezone(self):
        return get_timezone_for_user(None, self.domain)

    @use_angular_js
    @method_decorator(requires_privilege_with_fallback(privileges.DATA_CLEANUP)
                      )
    def dispatch(self, *args, **kwargs):
        return super(AutomaticUpdateRuleListView,
                     self).dispatch(*args, **kwargs)

    @property
    def page_context(self):
        return {
            'pagination_limit_cookie_name':
            ('hq.pagination.limit'
             '.automatic_update_rule_list.%s' % self.domain),
            'help_site_url':
            'https://confluence.dimagi.com/display/commcarepublic/Automatically+Close+Cases',
        }

    def _format_rule(self, rule):
        return {
            'id':
            rule.pk,
            'name':
            rule.name,
            'case_type':
            rule.case_type,
            'active':
            rule.active,
            'last_run': (ServerTime(rule.last_run).user_time(
                self.project_timezone).done().strftime(SERVER_DATETIME_FORMAT))
            if rule.last_run else '-',
            'edit_url':
            reverse(EditCaseRuleView.urlname, args=[self.domain, rule.pk]),
        }

    @allow_remote_invocation
    def get_pagination_data(self, in_data):
        try:
            limit = int(in_data['limit'])
            page = int(in_data['page'])
        except (TypeError, KeyError, ValueError):
            return {
                'success': False,
                'error': _("Please provide pagination info."),
            }

        start = (page - 1) * limit
        stop = limit * page

        rules = AutomaticUpdateRule.by_domain(
            self.domain,
            AutomaticUpdateRule.WORKFLOW_CASE_UPDATE,
            active_only=False,
        )

        rule_page = rules.order_by('name')[start:stop]
        total = rules.count()

        return {
            'response': {
                'itemList': list(map(self._format_rule, rule_page)),
                'total': total,
                'page': page,
            },
            'success': True,
        }

    @allow_remote_invocation
    def update_rule(self, in_data):
        try:
            rule_id = in_data['id']
        except KeyError:
            return {
                'error': _("Please provide an id."),
            }

        try:
            action = in_data['update_action']
        except KeyError:
            return {
                'error': _("Please provide an update_action."),
            }

        if action not in (
                self.ACTION_ACTIVATE,
                self.ACTION_DEACTIVATE,
                self.ACTION_DELETE,
        ):
            return {
                'error': _("Unrecognized update_action."),
            }

        try:
            rule = AutomaticUpdateRule.objects.get(
                pk=rule_id, workflow=AutomaticUpdateRule.WORKFLOW_CASE_UPDATE)
        except AutomaticUpdateRule.DoesNotExist:
            return {
                'error': _("Rule not found."),
            }

        if rule.domain != self.domain:
            return {
                'error': _("Rule not found."),
            }

        if action == self.ACTION_ACTIVATE:
            rule.activate()
        elif action == self.ACTION_DEACTIVATE:
            rule.activate(False)
        elif action == self.ACTION_DELETE:
            rule.soft_delete()

        return {
            'success': True,
        }
Esempio n. 29
0
class KeywordsListView(BaseMessagingSectionView, CRUDPaginatedViewMixin):
    template_name = 'reminders/keyword_list.html'
    urlname = 'keyword_list'
    page_title = ugettext_noop("Keywords")

    limit_text = ugettext_noop("keywords per page")
    empty_notification = ugettext_noop("You have no keywords. Please add one!")
    loading_message = ugettext_noop("Loading keywords...")

    @method_decorator(requires_privilege_with_fallback(privileges.INBOUND_SMS))
    def dispatch(self, *args, **kwargs):
        return super(KeywordsListView, self).dispatch(*args, **kwargs)

    @property
    def page_url(self):
        return reverse(self.urlname, args=[self.domain])

    @property
    def parameters(self):
        return self.request.POST if self.request.method == 'POST' else self.request.GET

    @property
    @memoized
    def total(self):
        return Keyword.get_by_domain(self.domain).count()

    @property
    def column_names(self):
        return [
            _("Keyword"),
            _("Description"),
            _("Action"),
        ]

    @property
    def page_context(self):
        return self.pagination_context

    @property
    def paginated_list(self):
        for keyword in Keyword.get_by_domain(
            self.domain,
            limit=self.limit,
            skip=self.skip,
        ):
            yield {
                'itemData': self._fmt_keyword_data(keyword),
                'template': 'keyword-row-template',
            }

    def _fmt_keyword_data(self, keyword):
        return {
            'id': keyword.couch_id,
            'keyword': keyword.keyword,
            'description': keyword.description,
            'editUrl': reverse(
                EditStructuredKeywordView.urlname,
                args=[self.domain, keyword.couch_id]
            ) if keyword.is_structured_sms() else reverse(
                EditNormalKeywordView.urlname,
                args=[self.domain, keyword.couch_id]
            ),
            'deleteModalId': 'delete-%s' % keyword.couch_id,
        }

    def _fmt_deleted_keyword_data(self, keyword):
        return {
            'keyword': keyword.keyword,
            'description': keyword.description,
        }

    def get_deleted_item_data(self, item_id):
        try:
            k = Keyword.objects.get(couch_id=item_id)
        except Keyword.DoesNotExist:
            raise Http404()

        if k.domain != self.domain:
            raise Http404()

        k.delete()

        return {
            'itemData': self._fmt_deleted_keyword_data(k),
            'template': 'keyword-deleted-template',
        }

    def post(self, *args, **kwargs):
        return self.paginate_crud_response
Esempio n. 30
0
class AddAutomaticUpdateRuleView(JSONResponseMixin, DataInterfaceSection):
    template_name = 'data_interfaces/add_automatic_update_rule.html'
    urlname = 'add_automatic_update_rule'
    page_title = ugettext_lazy("Add Automatic Case Close Rule")

    @property
    def page_url(self):
        return reverse(self.urlname, args=[self.domain])

    @property
    def initial_rule_form(self):
        return AddAutomaticCaseUpdateRuleForm(
            domain=self.domain,
            initial={
                'action': AddAutomaticCaseUpdateRuleForm.ACTION_CLOSE,
                'property_value_type': AutomaticUpdateAction.EXACT,
            })

    @property
    @memoized
    def rule_form(self):
        if self.request.method == 'POST':
            return AddAutomaticCaseUpdateRuleForm(self.request.POST,
                                                  domain=self.domain)
        else:
            return self.initial_rule_form

    @property
    def page_context(self):
        return {
            'form': self.rule_form,
        }

    @allow_remote_invocation
    def get_case_property_map(self):
        data = all_case_properties_by_domain(self.domain,
                                             include_parent_properties=False)
        return {
            'data': data,
            'success': True,
        }

    @use_angular_js
    @use_typeahead
    @method_decorator(requires_privilege_with_fallback(privileges.DATA_CLEANUP)
                      )
    def dispatch(self, *args, **kwargs):
        return super(AddAutomaticUpdateRuleView,
                     self).dispatch(*args, **kwargs)

    def create_criteria(self, rule):
        for condition in self.rule_form.cleaned_data['conditions']:
            AutomaticUpdateRuleCriteria.objects.create(
                rule=rule,
                property_name=condition['property_name'],
                property_value=condition['property_value'],
                match_type=condition['property_match_type'],
            )

    def create_actions(self, rule):
        if self.rule_form._closes_case():
            AutomaticUpdateAction.objects.create(
                rule=rule,
                action=AutomaticUpdateAction.ACTION_CLOSE,
            )
        if self.rule_form._updates_case():
            AutomaticUpdateAction.objects.create(
                rule=rule,
                action=AutomaticUpdateAction.ACTION_UPDATE,
                property_name=self.rule_form.
                cleaned_data['update_property_name'],
                property_value=self.rule_form.
                cleaned_data['update_property_value'],
                property_value_type=self.rule_form.
                cleaned_data['property_value_type'])

    def create_rule(self):
        with transaction.atomic():
            rule = AutomaticUpdateRule.objects.create(
                domain=self.domain,
                name=self.rule_form.cleaned_data['name'],
                case_type=self.rule_form.cleaned_data['case_type'],
                active=True,
                server_modified_boundary=self.rule_form.
                cleaned_data['server_modified_boundary'],
                filter_on_server_modified=self.rule_form.
                cleaned_data['filter_on_server_modified'],
                workflow=AutomaticUpdateRule.WORKFLOW_CASE_UPDATE,
            )
            self.create_criteria(rule)
            self.create_actions(rule)

    def post(self, request, *args, **kwargs):
        if self.rule_form.is_valid():
            self.create_rule()
            return HttpResponseRedirect(
                reverse(AutomaticUpdateRuleListView.urlname,
                        args=[self.domain]))
        # We can't call self.get() because JSONResponseMixin gets confused
        # since we're processing a post request. So instead we have to call
        # .get() directly on super(JSONResponseMixin, self), which correctly
        # is DataInterfaceSection in this case
        return super(JSONResponseMixin, self).get(request, *args, **kwargs)
Esempio n. 31
0
from __future__ import absolute_import
from __future__ import unicode_literals
from django.utils.decorators import method_decorator
from corehq import privileges
from corehq.apps.accounting.decorators import requires_privilege_with_fallback
from corehq.apps.reports.dispatcher import ReportDispatcher, ProjectReportDispatcher, datespan_default
from corehq.apps.users.decorators import require_permission
from corehq.apps.users.models import Permissions
from django_prbac.utils import has_privilege


require_can_edit_data = require_permission(Permissions.edit_data)

require_form_management_privilege = requires_privilege_with_fallback(privileges.DATA_CLEANUP)


class EditDataInterfaceDispatcher(ReportDispatcher):
    prefix = 'edit_data_interface'
    map_name = 'EDIT_DATA_INTERFACES'

    @method_decorator(require_can_edit_data)
    @datespan_default
    def dispatch(self, request, *args, **kwargs):
        from corehq.apps.case_importer.base import ImportCases
        from .interfaces import BulkFormManagementInterface

        if kwargs['report_slug'] == ImportCases.slug:
            return self.bulk_import_case_dispatch(request, *args, **kwargs)
        elif (kwargs['report_slug'] == BulkFormManagementInterface.slug and
              not kwargs.get('skip_permissions_check')):
            return self.bulk_form_management_dispatch(request, *args, **kwargs)
Esempio n. 32
0
class UploadCommCareUsers(BaseManageCommCareUserView):
    template_name = 'users/upload_commcare_users.html'
    urlname = 'upload_commcare_users'
    page_title = ugettext_noop("Bulk Upload Mobile Workers")

    @method_decorator(
        requires_privilege_with_fallback(privileges.BULK_USER_MANAGEMENT))
    def dispatch(self, request, *args, **kwargs):
        return super(UploadCommCareUsers,
                     self).dispatch(request, *args, **kwargs)

    @property
    def page_context(self):
        context = {
            'bulk_upload': {
                "help_site": {
                    "address": BULK_MOBILE_HELP_SITE,
                    "name": _("CommCare Help Site"),
                },
                "download_url":
                reverse("download_commcare_users", args=(self.domain, )),
                "adjective":
                _("mobile worker"),
                "plural_noun":
                _("mobile workers"),
            },
            'show_secret_settings': self.request.REQUEST.get("secret", False),
        }
        context.update({
            'bulk_upload_form': get_bulk_upload_form(context),
        })
        return context

    def post(self, request, *args, **kwargs):
        upload = request.FILES.get('bulk_upload_file')
        """View's dispatch method automatically calls this"""
        try:
            self.workbook = WorkbookJSONReader(upload)
        except InvalidFileException:
            try:
                csv.DictReader(
                    io.StringIO(upload.read().decode('ascii'), newline=None))
                return HttpResponseBadRequest(
                    "CommCare HQ no longer supports CSV upload. "
                    "Please convert to Excel 2007 or higher (.xlsx) "
                    "and try again.")
            except UnicodeDecodeError:
                return HttpResponseBadRequest("Unrecognized format")
        except JSONReaderError as e:
            messages.error(request,
                           'Your upload was unsuccessful. %s' % e.message)
            return self.get(request, *args, **kwargs)
        except HeaderValueError as e:
            return HttpResponseBadRequest(
                "Upload encountered a data type error: %s" % e.message)

        try:
            self.user_specs = self.workbook.get_worksheet(title='users')
        except WorksheetNotFound:
            try:
                self.user_specs = self.workbook.get_worksheet()
            except WorksheetNotFound:
                return HttpResponseBadRequest("Workbook has no worksheets")

        try:
            self.group_specs = self.workbook.get_worksheet(title='groups')
        except WorksheetNotFound:
            self.group_specs = []

        self.location_specs = []
        if Domain.get_by_name(self.domain).commtrack_enabled:
            try:
                self.location_specs = self.workbook.get_worksheet(
                    title='locations')
            except WorksheetNotFound:
                # if there is no sheet for locations (since this was added
                # later and is optional) we don't error
                pass

        try:
            check_headers(self.user_specs)
        except UserUploadError as e:
            return HttpResponseBadRequest(e)

        task_ref = expose_download(None, expiry=1 * 60 * 60)
        task = bulk_upload_async.delay(self.domain, list(self.user_specs),
                                       list(self.group_specs),
                                       list(self.location_specs))
        task_ref.set_task(task)
        return HttpResponseRedirect(
            reverse(UserUploadStatusView.urlname,
                    args=[self.domain, task_ref.download_id]))
Esempio n. 33
0
from corehq.apps.users.decorators import require_permission
from corehq.apps.users.models import Permissions
from dimagi.utils.decorators.memoized import memoized
from .models import UI_SIMPLE_FIXED, UI_COMPLEX
from .util import can_use_survey_reminders, get_form_list, get_form_name, get_recipient_name
from corehq.apps.domain.models import Domain
from corehq.util.timezones.utils import get_timezone_for_user
from custom.ewsghana.forms import EWSBroadcastForm

ACTION_ACTIVATE = 'activate'
ACTION_DEACTIVATE = 'deactivate'
ACTION_DELETE = 'delete'

reminders_framework_permission = lambda *args, **kwargs: (
    require_permission(Permissions.edit_data)(
        requires_privilege_with_fallback(privileges.REMINDERS_FRAMEWORK)(*args, **kwargs)
    )
)

survey_reminders_permission = lambda *args, **kwargs: (
    require_permission(Permissions.edit_data)(
        requires_privilege_with_fallback(privileges.INBOUND_SMS)(*args, **kwargs)
    )
)


def get_project_time_info(domain):
    timezone = get_timezone_for_user(None, domain)
    now = pytz.utc.localize(datetime.utcnow())
    timezone_now = now.astimezone(timezone)
    return (timezone, now, timezone_now)
Esempio n. 34
0
from corehq.apps.users.decorators import require_permission
from corehq.apps.users.models import Permissions
from dimagi.utils.decorators.memoized import memoized
from .models import UI_SIMPLE_FIXED, UI_COMPLEX
from .util import can_use_survey_reminders, get_form_list, get_form_name, get_recipient_name
from corehq.apps.domain.models import Domain
from corehq.util.timezones.utils import get_timezone_for_user
from custom.ewsghana.forms import EWSBroadcastForm

ACTION_ACTIVATE = "activate"
ACTION_DEACTIVATE = "deactivate"
ACTION_DELETE = "delete"

reminders_framework_permission = lambda *args, **kwargs: (
    require_permission(Permissions.edit_data)(
        requires_privilege_with_fallback(privileges.REMINDERS_FRAMEWORK)(*args, **kwargs)
    )
)

survey_reminders_permission = lambda *args, **kwargs: (
    require_permission(Permissions.edit_data)(requires_privilege_with_fallback(privileges.INBOUND_SMS)(*args, **kwargs))
)


def get_project_time_info(domain):
    timezone = get_timezone_for_user(None, domain)
    now = pytz.utc.localize(datetime.utcnow())
    timezone_now = now.astimezone(timezone)
    return (timezone, now, timezone_now)