Example #1
0
def paragraph(content, context, before=1, after=1, editable=False, style=None):
    try:
        status = context[u"_paragraph_status"]
    except KeyError:
        status = Bunch(opened=False, after=0)
        context[u"_paragraph_status"] = status
    html = []
    if status.opened and before > 0:
        html.append(format_html(u"</div>"))
        status.opened = False
    if not status.opened:
        spacing = max(before, status.after)
        attrs = defaultdict(list)
        if editable:
            attrs[u"class"].append(u"editable-container")
        if spacing > 1:
            attrs[u"style"].append(u"margin-top: {0}ex;".format(spacing))
        if style:
            attrs[u"style"].append(style)
        html.append(format_html_tag(u"div", attrs))
        html.append(u"        ")
        status.opened = True
    html.append(format_html(u"{0}", content))
    if after > 0:
        html.append(format_html(u"</div>"))
        status.opened = False
    status.after = after
    html = format_html_join(u"", u"{0}", zip(html))
    return html
    def _create_scenario(self, **kwargs):
        res = Bunch()

        inforequest_args = kwargs.pop(u'inforequest_args', [])
        res.inforequest, res.branch, res.actions = self._create_inforequest_scenario(*inforequest_args)

        email_args = kwargs.pop(u'email_args', {})
        email_args.setdefault(u'inforequest', res.inforequest)
        res.email, res.rel = self._create_inforequest_email(**email_args)

        self.assertEqual(kwargs, {})
        return res
Example #3
0
    def _create_scenario(self, **kwargs):
        res = Bunch()

        inforequest_args = kwargs.pop(u'inforequest_args', [])
        res.inforequest, res.branch, res.actions = self._create_inforequest_scenario(
            *inforequest_args)

        email_args = kwargs.pop(u'email_args', {})
        email_args.setdefault(u'inforequest', res.inforequest)
        res.email, res.rel = self._create_inforequest_email(**email_args)

        self.assertEqual(kwargs, {})
        return res
Example #4
0
    def _create_scenario(self, **kwargs):
        res = Bunch()

        inforequest_args = kwargs.pop(u'inforequest_args', [])
        inforequest_scenario = kwargs.pop(u'inforequest_scenario', self.good_scenario)
        inforequest_args = list(inforequest_args) + list(inforequest_scenario)
        res.inforequest, res.branch, res.actions = self._create_inforequest_scenario(*inforequest_args)

        draft_args = kwargs.pop(u'draft_args', None)
        if draft_args is not None:
            draft_args.setdefault(u'inforequest', res.inforequest)
            draft_args.setdefault(u'type', self.action_type)
            res.draft = self._create_action_draft(**draft_args)

        self.assertEqual(kwargs, {})
        return res
    def _create_scenario(self, **kwargs):
        res = Bunch()

        inforequest_args = kwargs.pop(u'inforequest_args', [])
        inforequest_scenario = kwargs.pop(u'inforequest_scenario', self.good_scenario)
        inforequest_args = list(inforequest_args) + list(inforequest_scenario)
        res.inforequest, res.branch, res.actions = self._create_inforequest_scenario(*inforequest_args)

        draft_args = kwargs.pop(u'draft_args', None)
        if draft_args is not None:
            draft_args.setdefault(u'inforequest', res.inforequest)
            draft_args.setdefault(u'type', self.action_type)
            res.draft = self._create_action_draft(**draft_args)

        self.assertEqual(kwargs, {})
        return res
Example #6
0
    def _run_mail_cron_job(self,
                           status_code=200,
                           response={},
                           delete_settings=(),
                           **override_settings):
        overrides = {
            u'EMAIL_OUTBOUND_TRANSPORT':
            u'poleno.mail.transports.mandrill.MandrillTransport',
            u'EMAIL_INBOUND_TRANSPORT': None,
            u'MANDRILL_API_KEY': u'default_testing_api_key',
            u'MANDRILL_API_URL': u'https://defaulttestinghost/api'
        }
        overrides.update(override_settings)

        requests = mock.Mock()
        requests.post.return_value.status_code = status_code
        requests.post.return_value.text = u'Response text'
        requests.post.return_value.json.return_value = response

        with self.settings(**overrides):
            for name in delete_settings:
                delattr(settings, name)
            with mock.patch(
                    u'poleno.mail.transports.mandrill.transport.requests',
                    requests):
                with override_signals(message_sent, message_received):
                    mail_cron_job().do()

        posts = [
            Bunch(url=call[0][0], data=json.loads(call[1][u'data']))
            for call in requests.post.call_args_list
        ]
        return posts
    def _create_scenario(self, **kwargs):
        res = Bunch()

        created = kwargs.pop(u'created', u'2010-03-07 10:33:00')
        timewarp.jump(local_datetime_from_local(created))

        inforequest_args = kwargs.pop(u'inforequest_args', [])
        inforequest_scenario = kwargs.pop(u'inforequest_scenario', [u'request'])
        inforequest_args = list(inforequest_args) + list(inforequest_scenario)
        res.inforequest, res.branch, res.actions = self._create_inforequest_scenario(*inforequest_args)
        res.action = res.branch.last_action

        now = kwargs.pop(u'now', u'2010-07-08 10:33:00')
        timewarp.jump(local_datetime_from_local(now))

        self.assertEqual(kwargs, {})
        self._scenario = res
        return res
    def _create_scenario(self, **kwargs):
        res = Bunch()

        created = kwargs.pop(u'created', u'2010-03-07 10:33:00')
        timewarp.jump(local_datetime_from_local(created))

        inforequest_args = kwargs.pop(u'inforequest_args', [])
        inforequest_scenario = kwargs.pop(u'inforequest_scenario',
                                          [u'request'])
        inforequest_args = list(inforequest_args) + list(inforequest_scenario)
        res.inforequest, res.branch, res.actions = self._create_inforequest_scenario(
            *inforequest_args)
        res.action = res.branch.last_action

        now = kwargs.pop(u'now', u'2010-07-08 10:33:00')
        timewarp.jump(local_datetime_from_local(now))

        self.assertEqual(kwargs, {})
        self._scenario = res
        return res
Example #9
0
def paragraph(content, context, before=1, after=1, editable=False, style=None):
    try:
        status = context[u'_paragraph_status']
    except KeyError:
        status = Bunch(opened=False, after=0)
        context[u'_paragraph_status'] = status
    html = []
    if status.opened and before > 0:
        html.append(format_html(u'</div>'))
        status.opened = False
    if not status.opened:
        spacing = max(before, status.after)
        attrs = defaultdict(list)
        if editable:
            attrs[u'class'].append(u'editable-container')
        if spacing > 1:
            attrs[u'style'].append(u'margin-top: {0}ex;'.format(spacing))
        if style:
            attrs[u'style'].append(style)
        html.append(format_html_tag(u'div', attrs))
        html.append(u'        ')
        status.opened = True
    html.append(format_html(u'{0}', content))
    if after > 0:
        html.append(format_html(u'</div>'))
        status.opened = False
    status.after = after
    html = format_html_join(u'', u'{0}', zip(html))
    return html
Example #10
0
 def test_call_method_filters(self):
     u"""
     Tests ``obj|method:"name"|arg:arg|call`` and ``obj|method:"name"|call_with:arg`` filters
     for calling object methods with arguments.
     """
     a = Bunch(plus=(lambda a, b: a + b))
     b = {'minus': (lambda a, b: a - b)}
     rendered = self._render(
         u'{% load method call_with call with from poleno.utils %}'
         u'({{ a|method:"plus"|with:10|with:11|call }})'
         u'({{ b|method:"minus"|with:5|call_with:10 }})'
         u'({{ a|method:"nop"|call_with:3 }})'  # invalid method
         u'({{ b|method:"nop"|with:a|call }})'  # invalid method
         u'',
         a=a,
         b=b)
     self.assertEqual(rendered, u'(21)(-5)([not callable])([not callable])')
Example #11
0
 def _run_mail_cron_job(self):
     connection = mock.Mock()
     with self.settings(EMAIL_OUTBOUND_TRANSPORT=
                        u'poleno.mail.transports.smtp.SmtpTransport',
                        EMAIL_INBOUND_TRANSPORT=None):
         with mock.patch(u'poleno.mail.transports.smtp.get_connection',
                         return_value=connection):
             with override_signals(message_sent, message_received):
                 mail_cron_job().do()
     res = []
     for call in connection.send_messages.call_args_list:
         for mail in call[0][0]:
             as_bytes = mail.message().as_bytes()
             headers, body = as_bytes.split(u'\n\n', 1)
             headers = self._parse_headers(headers)
             res.append(Bunch(headers=headers, body=body,
                              as_bytes=as_bytes))
     return res
Example #12
0
    def __init__(self, page, file, create, *args, **kwargs):
        super(FileEditForm, self).__init__(*args, **kwargs)
        self.page = page
        self.file = file
        self.create = create

        self.fieldsets = [
            Bunch(label=None, collapse=False, fields=[]),
        ]

        self.fieldsets[0].fields.append(
            FakeField(u'Page', LivePath.render(page.lang, page.path)))

        self.fields[u'name'] = forms.CharField(
            label=u'File Name',
            validators=[
                RegexValidator(
                    pages.file_regex,
                    squeeze(u"""
                    Enter a valid file name. Only letters, numbers, dashes and dots are allowed.
                    """)),
            ],
        )
        if not create:
            self.initial[u'name'] = file.name
        self.fieldsets[0].fields.append(self[u'name'])

        if not create:
            self.fieldsets[0].fields.append(
                FakeField(u'Content Type', file.content_type))
            self.fieldsets[0].fields.append(
                FakeField(
                    u'Modified',
                    date_format(datetime.datetime.fromtimestamp(file.mtime),
                                u'DATETIME_FORMAT')))

        self.fields[u'content'] = forms.FileField(
            label=u'Content',
            widget=PreviewFileInput,
        )
        if not create:
            self.initial[u'content'] = file
        self.fieldsets[0].fields.append(self[u'content'])
Example #13
0
    def save_objects(self, rows):
        errors = 0
        stats = Bunch(created=0, changed=0, unchanged=0, deleted=0)
        originals = {o.pk: o for o in self.model.objects.all()}
        for row_idx, values in rows.items():
            try:
                pk = values[self.columns.pk.label]
                original = originals.pop(pk, None)
                fields = self.process_fields(row_idx, values, original)
                assert fields[u'pk'] == pk

                obj, has_changed = self.save_object(fields, original)
                if has_changed and original:
                    self.importer.write(2, u'Changed {}', self.obj_repr(obj))
                    stats.changed += 1
                elif has_changed:
                    self.importer.write(2, u'Created {}', self.obj_repr(obj))
                    stats.created += 1
                else:
                    stats.unchanged += 1
            except RollingError as e:
                errors += e.count
        # Mark omitted instances for deletion or add an error if delete is not permitted. Don't
        # delete anything if there were any errors as we don't know which instances are missing for
        # real and which are missing because of errors. The instances will be deleted only after
        # all sheets are imported to prevent unintentional cascades.
        if errors:
            raise RollingError(errors)
        for obj in originals.values():
            if self.delete_omitted:
                self.book.marked_for_deletion[obj] = self
                stats.deleted += 1
            else:
                self.error(u'omitted', u'Omitted {}', self.obj_repr(obj))
                errors += 1
        if errors:
            raise RollingError(errors)
        return stats
Example #14
0
 def test_undefined_attribute(self):
     bunch = Bunch(first=1, second=u'second')
     with self.assertRaises(AttributeError):
         bunch.nop
Example #15
0
    def test_can_add_x_properties(self):
        tests = (
            (Action.TYPES.REQUEST,
             Bunch(
                 scenario=[],
                 allowed=[
                     u'confirmation', u'extension', u'advancement',
                     u'clarification_request', u'disclosure', u'refusal',
                     u'obligee_action', u'obligee_email_action'
                 ],
                 expired=[
                     u'appeal', u'confirmation', u'extension', u'advancement',
                     u'clarification_request', u'disclosure', u'refusal',
                     u'obligee_action', u'obligee_email_action',
                     u'applicant_action'
                 ],
             )),
            (Action.TYPES.CLARIFICATION_RESPONSE,
             Bunch(
                 scenario=[
                     u'clarification_request', u'clarification_response'
                 ],
                 allowed=[
                     u'extension', u'advancement', u'clarification_request',
                     u'disclosure', u'refusal', u'obligee_action',
                     u'obligee_email_action'
                 ],
                 expired=[
                     u'appeal', u'extension', u'advancement',
                     u'clarification_request', u'disclosure', u'refusal',
                     u'obligee_action', u'obligee_email_action',
                     u'applicant_action'
                 ],
             )),
            (Action.TYPES.APPEAL,
             Bunch(
                 scenario=[u'expiration', u'appeal'],
                 allowed=[
                     u'affirmation', u'reversion', u'remandment',
                     u'obligee_action'
                 ],
                 expired=[
                     u'affirmation', u'reversion', u'remandment',
                     u'obligee_action'
                 ],
             )),
            (Action.TYPES.CONFIRMATION,
             Bunch(
                 scenario=[u'confirmation'],
                 allowed=[
                     u'extension', u'advancement', u'clarification_request',
                     u'disclosure', u'refusal', u'obligee_action',
                     u'obligee_email_action'
                 ],
                 expired=[
                     u'appeal', u'extension', u'advancement',
                     u'clarification_request', u'disclosure', u'refusal',
                     u'obligee_action', u'obligee_email_action',
                     u'applicant_action'
                 ],
             )),
            (Action.TYPES.EXTENSION,
             Bunch(
                 scenario=[u'extension'],
                 allowed=[
                     u'disclosure', u'refusal', u'obligee_action',
                     u'obligee_email_action'
                 ],
                 expired=[
                     u'appeal', u'disclosure', u'refusal', u'obligee_action',
                     u'obligee_email_action', u'applicant_action'
                 ],
             )),
            (Action.TYPES.ADVANCEMENT,
             Bunch(
                 scenario=[u'advancement'],
                 allowed=[u'appeal', u'applicant_action'],
                 expired=[u'appeal', u'applicant_action'],
             )),
            (Action.TYPES.CLARIFICATION_REQUEST,
             Bunch(
                 scenario=[u'clarification_request'],
                 allowed=[
                     u'clarification_response', u'clarification_request',
                     u'obligee_action', u'obligee_email_action',
                     u'applicant_action', u'applicant_email_action'
                 ],
                 expired=[
                     u'clarification_response', u'clarification_request',
                     u'obligee_action', u'obligee_email_action',
                     u'applicant_action', u'applicant_email_action'
                 ],
             )),
            (Action.TYPES.DISCLOSURE,
             Bunch(
                 scenario=[
                     (u'disclosure',
                      dict(disclosure_level=Action.DISCLOSURE_LEVELS.NONE))
                 ],
                 allowed=[u'appeal', u'applicant_action'],
                 expired=[u'appeal', u'applicant_action'],
             )),
            (Action.TYPES.DISCLOSURE,
             Bunch(
                 scenario=[
                     (u'disclosure',
                      dict(disclosure_level=Action.DISCLOSURE_LEVELS.PARTIAL))
                 ],
                 allowed=[u'appeal', u'applicant_action'],
                 expired=[u'appeal', u'applicant_action'],
             )),
            (Action.TYPES.DISCLOSURE,
             Bunch(
                 scenario=[
                     (u'disclosure',
                      dict(disclosure_level=Action.DISCLOSURE_LEVELS.FULL))
                 ],
                 allowed=[],
                 expired=[],
             )),
            (Action.TYPES.REFUSAL,
             Bunch(
                 scenario=[u'refusal'],
                 allowed=[u'appeal', u'applicant_action'],
                 expired=[u'appeal', u'applicant_action'],
             )),
            (Action.TYPES.AFFIRMATION,
             Bunch(
                 scenario=[u'refusal', u'appeal', u'affirmation'],
                 allowed=[],
                 expired=[],
             )),
            (Action.TYPES.REVERSION,
             Bunch(
                 scenario=[u'refusal', u'appeal', u'reversion'],
                 allowed=[],
                 expired=[],
             )),
            (Action.TYPES.REMANDMENT,
             Bunch(
                 scenario=[u'refusal', u'appeal', u'remandment'],
                 allowed=[
                     u'extension', u'disclosure', u'refusal',
                     u'obligee_action', u'obligee_email_action'
                 ],
                 expired=[
                     u'appeal', u'extension', u'disclosure', u'refusal',
                     u'obligee_action', u'obligee_email_action',
                     u'applicant_action'
                 ],
             )),
            (Action.TYPES.ADVANCED_REQUEST,
             Bunch(
                 branch=1,
                 scenario=[u'advancement'],
                 allowed=[
                     u'confirmation', u'extension', u'advancement',
                     u'clarification_request', u'disclosure', u'refusal',
                     u'obligee_action', u'obligee_email_action'
                 ],
                 expired=[
                     u'appeal', u'confirmation', u'extension', u'advancement',
                     u'clarification_request', u'disclosure', u'refusal',
                     u'obligee_action', u'obligee_email_action',
                     u'applicant_action'
                 ],
             )),
            (Action.TYPES.EXPIRATION,
             Bunch(
                 scenario=[u'expiration'],
                 allowed=[u'appeal', u'applicant_action'],
                 expired=[u'appeal', u'applicant_action'],
             )),
            (Action.TYPES.APPEAL_EXPIRATION,
             Bunch(
                 scenario=[u'refusal', u'appeal', u'appeal_expiration'],
                 allowed=[],
                 expired=[],
             )),
        )
        # Make sure we are testing all defined action types
        tested_action_types = set(a for a, _ in tests)
        defined_action_types = Action.TYPES._inverse.keys()
        self.assertItemsEqual(tested_action_types, defined_action_types)

        can_add_properties = (
            u'request',
            u'clarification_response',
            u'appeal',
            u'confirmation',
            u'extension',
            u'advancement',
            u'clarification_request',
            u'disclosure',
            u'refusal',
            u'affirmation',
            u'reversion',
            u'remandment',
            u'applicant_action',
            u'applicant_email_action',
            u'obligee_action',
            u'obligee_email_action',
        )

        for action_type, test in tests:
            timewarp.jump(local_datetime_from_local(u'2010-07-05 10:33:00'))
            objs = self._create_inforequest_scenario(*test.scenario)
            branch = getattr(test, u'branch', 0)
            branch = [o for o in flatten(objs)
                      if isinstance(o, Branch)][branch]
            self.assertEqual(branch.last_action.type, action_type)

            # Check actions allowed when the last action deadline is not expired yet
            for can_add_property in can_add_properties:
                value = getattr(branch, u'can_add_%s' % can_add_property)
                expected = can_add_property in test.allowed
                self.assertEqual(
                    value, expected, u'can_add_%s is %s after %r' %
                    (can_add_property, value, test.scenario))

            # Check actions allowed when the last action deadline is expired
            timewarp.jump(local_datetime_from_local(u'2010-10-05 10:33:00'))
            branch = Branch.objects.get(pk=branch.pk)
            for can_add_property in can_add_properties:
                value = getattr(branch, u'can_add_%s' % can_add_property)
                expected = can_add_property in test.expired
                self.assertEqual(
                    value, expected, u'can_add_%s is %s after expired %r' %
                    (can_add_property, value, test.scenario))
Example #16
0
 def test_add_attribute(self):
     bunch = Bunch(first=1, second=u'second')
     with self.assertRaises(AttributeError):
         bunch.third
     bunch.third = [1, 2, 3]
     self.assertEqual(bunch.third, [1, 2, 3])
Example #17
0
    def __init__(self, page, create, *args, **kwargs):
        super(PageEditForm, self).__init__(*args, **kwargs)
        self.page = page
        self.create = create

        edit_root = not create and page.is_root
        edit_redirect = not create and page.is_redirect
        edit_regular = not create and not page.is_root and not page.is_redirect
        assert bool(create) + bool(edit_root) + bool(edit_redirect) + bool(
            edit_regular) == 1

        self.fieldsets = [
            Bunch(label=None, collapse=False, fields=[]),
            Bunch(label=u'Raw Config', collapse=True, fields=[]),
            Bunch(label=u'Page Content', collapse=False, fields=[]),
        ]

        if edit_regular:
            self.fields[u'parent'] = forms.CharField(
                label=u'URL Path',
                validators=[
                    RegexValidator(
                        pages.path_regex,
                        squeeze(u"""
                        Enter a valid path. It must be an absolute path with respect to the root
                        page starting and ending with a slash.
                        """)),
                ],
                widget=forms.TextInput(
                    attrs={
                        u'class':
                        u'popup-path',
                        u'style':
                        u'width: 50em;',
                        u'data-popup-url':
                        reverse(u'admin:pages_index', args=[page.lang]),
                        u'data-icon':
                        staticfiles_storage.url(
                            u'admin/img/selector-search.gif'),
                    }),
            )
            self.initial[u'parent'] = page.ppath
            self.fieldsets[0].fields.append(self[u'parent'])
            self.fieldsets[0].fields.append(
                LivePath(page.lang, self[u'parent']))
        else:
            self.fieldsets[0].fields.append(FakeField(u'URL Path', page.path))

        if create or edit_regular:
            self.fields[u'name'] = forms.CharField(
                label=u'URL Name',
                validators=[
                    RegexValidator(
                        pages.slug_regex,
                        u'Enter a valid slug. Only letters, numbers and dashes are allowed.'
                    ),
                ],
            )
            if not create:
                self.initial[u'name'] = page.name
            self.fieldsets[0].fields.append(self[u'name'])

        if create or edit_root or edit_regular:
            self.fields[u'title'] = forms.CharField(
                label=u'Page Title',
                required=False,
                widget=forms.TextInput(attrs={
                    u'style': u'width: 50em;',
                }),
            )
            if not create:
                self.initial[u'title'] = page._config.get(u'title')
            self.fieldsets[0].fields.append(self[u'title'])

        if create or edit_root or edit_regular:
            self.fields[u'label'] = forms.CharField(
                label=u'Menu Label',
                required=False,
            )
            if not create:
                self.initial[u'label'] = page._config.get(u'label')
            self.fieldsets[0].fields.append(self[u'label'])

        if create or edit_regular:
            self.fields[u'order'] = forms.CharField(
                label=u'Sort Key',
                required=False,
            )
            if not create:
                self.initial[u'order'] = page._config.get(u'order')
            self.fieldsets[0].fields.append(self[u'order'])

        if create or edit_regular:
            for lang, _ in settings.LANGUAGES:
                if lang != page.lang:
                    key = u'lang_{}'.format(lang)
                    self.fields[key] = forms.CharField(
                        label=u'Translation {}'.format(lang.upper()),
                        required=False,
                        validators=[
                            RegexValidator(
                                pages.path_regex,
                                squeeze(u"""
                                Enter a valid path. It must be an absolute path with respect to the
                                root page starting and ending with a slash.
                                """)),
                        ],
                        widget=forms.TextInput(
                            attrs={
                                u'class':
                                u'popup-path',
                                u'style':
                                u'width: 50em;',
                                u'data-popup-url':
                                reverse(u'admin:pages_index', args=[lang]),
                                u'data-icon':
                                staticfiles_storage.url(
                                    u'admin/img/selector-search.gif'),
                            }),
                    )
                    if not create:
                        self.initial[key] = page._config.get(key)
                    self.fieldsets[0].fields.append(self[key])
                    self.fieldsets[0].fields.append(LivePath(lang, self[key]))

        if edit_redirect:
            self.fields[u'redirect'] = forms.CharField(
                label=u'Redirect',
                validators=[
                    RegexValidator(
                        pages.path_regex,
                        squeeze(u"""
                        Enter a valid path. It must be an absolute path with respect to the root
                        page starting and ending with a slash.
                        """)),
                ],
                widget=forms.TextInput(
                    attrs={
                        u'class':
                        u'popup-path',
                        u'style':
                        u'width: 50em;',
                        u'data-popup-url':
                        reverse(u'admin:pages_index', args=[page.lang]),
                        u'data-icon':
                        staticfiles_storage.url(
                            u'admin/img/selector-search.gif'),
                    }),
            )
            self.initial[u'redirect'] = page.redirect_path
            self.fieldsets[0].fields.append(self[u'redirect'])
            self.fieldsets[0].fields.append(
                LivePath(page.lang, self[u'redirect']))

        if create or edit_root or edit_regular:
            self.fields[u'disabled'] = forms.BooleanField(
                label=u'Disabled',
                required=False,
            )
            if not create:
                self.initial[u'disabled'] = bool(page._config.get(u'disabled'))
            self.fieldsets[0].fields.append(self[u'disabled'])

        if create or edit_root or edit_regular:
            self.fields[u'raw'] = forms.CharField(
                label=u'',
                required=False,
                widget=forms.Textarea(attrs={
                    u'style': u'width: 100%; height: 10em;',
                }),
            )
            if not create:
                self.initial[u'raw'] = page.raw_config
            self.fieldsets[1].fields.append(self[u'raw'])

        if create or edit_root or edit_regular:
            with translation(page.lang):
                url = reverse(u'admin:pages_preview')
            self.fields[u'template'] = forms.CharField(
                label=u'',
                required=False,
                widget=forms.Textarea(attrs={
                    u'class': u'template-widget',
                    u'data-url': url,
                }),
            )
            if not create:
                self.initial[u'template'] = page.template
            self.fieldsets[2].fields.append(self[u'template'])
Example #18
0
 def test_change_attribute(self):
     bunch = Bunch(first=1, second=u'second')
     self.assertEqual(bunch.first, 1)
     bunch.first = u'changed'
     self.assertEqual(bunch.first, u'changed')
Example #19
0
class Deadline(FormatMixin, object):

    TYPES = Bunch(
            OBLIGEE_DEADLINE = 1,
            APPLICANT_DEADLINE = 2,
            )

    UNITS = Bunch(
            CALENDAR_DAYS = 1,
            WORKDAYS = 2,
            )

    def __init__(self, type, base_date, value, unit, snooze):
        self.type = type
        self.base_date = base_date
        self.value = value
        self.unit = unit
        self._snooze = snooze
        self._today = local_today()

    @property
    def is_obligee_deadline(self):
        return self.type == self.TYPES.OBLIGEE_DEADLINE

    @property
    def is_applicant_deadline(self):
        return self.type == self.TYPES.APPLICANT_DEADLINE

    @property
    def is_in_calendar_days(self):
        return self.unit == self.UNITS.CALENDAR_DAYS

    @property
    def is_in_workdays(self):
        return self.unit == self.UNITS.WORKDAYS

    @cached_property
    def deadline_date(self):
        if self.is_in_calendar_days:
            return self.base_date + datetime.timedelta(days=self.value)
        else:
            return workdays.advance(self.base_date, self.value)

    @cached_property
    def calendar_days_passed(self):
        return self.calendar_days_passed_at(self._today)

    def calendar_days_passed_at(self, at):
        return (at - self.base_date).days

    @cached_property
    def workdays_passed(self):
        return self.workdays_passed(self._today)

    def workdays_passed_at(self, at):
        return workdays.between(self.base_date, at)

    @cached_property
    def calendar_days_remaining(self):
        return self.calendar_days_remaining_at(self._today)

    def calendar_days_remaining_at(self, at):
        return (self.deadline_date - at).days

    @cached_property
    def workdays_remaining(self):
        return self.workdays_remaining_at(self._today)

    def workdays_remaining_at(self, at):
        return workdays.between(at, self.deadline_date)

    @cached_property
    def calendar_days_behind(self):
        return -self.calendar_days_remaining

    def calendar_days_behind_at(self, at):
        return -self.calendar_days_remaining_at(at)

    @cached_property
    def workdays_behind(self):
        return -self.workdays_remaining

    def workdays_behind_at(self, at):
        return -self.workdays_remaining_at(at)

    @cached_property
    def is_deadline_missed(self):
        return self.is_deadline_missed_at(self._today)

    def is_deadline_missed_at(self, at):
        return self.deadline_date < at

    @cached_property
    def snooze_date(self):
        res = self._snooze or self.deadline_date
        res = max(res, self.deadline_date)
        res = min(res, self.deadline_date + datetime.timedelta(days=8))
        return res

    @cached_property
    def is_snoozed(self):
        return self.snooze_date > self.deadline_date

    @cached_property
    def snooze_in_calendar_days(self):
        return self.calendar_days_behind_at(self.snooze_date)

    @cached_property
    def snooze_in_workdays(self):
        return self.workdays_behind_at(self.snooze_date)

    @cached_property
    def snooze_calendar_days_remaining(self):
        return self.snooze_calendar_days_remaining_at(self._today)

    def snooze_calendar_days_remaining_at(self, at):
        return (self.snooze_date - at).days

    @cached_property
    def snooze_workdays_remaining(self):
        return self.snooze_workdays_remaining_at(self._today)

    def snooze_workdays_remaining_at(self, at):
        return workdays.between(at, self.snooze_date)

    @cached_property
    def snooze_calendar_days_behind(self):
        return -self.snooze_calendar_days_remaining

    def snooze_calendar_days_behind_at(self, at):
        return -self.snooze_calendar_days_remaining_at(at)

    @cached_property
    def snooze_workdays_behind(self):
        return -self.snooze_workdays_remaining

    def snooze_workdays_behind_at(self, at):
        return -self.snooze_workdays_remaining_at(at)

    @cached_property
    def is_snooze_missed(self):
        return self.is_snooze_missed_at(self._today)

    def is_snooze_missed_at(self, at):
        return self.snooze_date < at


    def __unicode__(self):
        return u'{} {} for {} since {}{}'.format(
                self.value,
                u'CD' if self.is_in_calendar_days else u'WD',
                u'Applicant' if self.is_obligee_deadline else u'Obligee',
                self.base_date,
                u' +{0} CD'.format(self.snooze_in_calendar_days)
                    if self.snooze_date != self.deadline_date else u'',
                )
Example #20
0
 def test_change_attribute(self):
     bunch = Bunch(first=1, second=u'second')
     self.assertEqual(bunch.first, 1)
     bunch.first = u'changed'
     self.assertEqual(bunch.first, u'changed')
Example #21
0
 def test_defined_attribute(self):
     bunch = Bunch(first=1, second=u'second')
     self.assertEqual(bunch.first, 1)
     self.assertEqual(bunch.second, u'second')
Example #22
0
 def test_add_attribute(self):
     bunch = Bunch(first=1, second=u'second')
     with self.assertRaises(AttributeError):
         bunch.third
     bunch.third = [1, 2, 3]
     self.assertEqual(bunch.third, [1, 2, 3])
Example #23
0
 def test_delete_attribute(self):
     bunch = Bunch(first=1, second=u'second')
     self.assertEqual(bunch.first, 1)
     del bunch.first
     with self.assertRaises(AttributeError):
         bunch.first
Example #24
0
class Action(models.Model):
    # May NOT be NULL
    branch = models.ForeignKey(u'Branch')

    # NOT NULL for actions sent or received by email; NULL otherwise
    email = models.OneToOneField(u'mail.Message',
                                 blank=True,
                                 null=True,
                                 on_delete=models.SET_NULL)

    # May NOT be NULL
    TYPES = FieldChoices(
        # Applicant actions
        (u'REQUEST', 1, _(u'inforequests:Action:type:REQUEST')),
        (u'CLARIFICATION_RESPONSE', 12,
         _(u'inforequests:Action:type:CLARIFICATION_RESPONSE')),
        (u'APPEAL', 13, _(u'inforequests:Action:type:APPEAL')),
        # Obligee actions
        (u'CONFIRMATION', 2, _(u'inforequests:Action:type:CONFIRMATION')),
        (u'EXTENSION', 3, _(u'inforequests:Action:type:EXTENSION')),
        (u'ADVANCEMENT', 4, _(u'inforequests:Action:type:ADVANCEMENT')),
        (u'CLARIFICATION_REQUEST', 5,
         _(u'inforequests:Action:type:CLARIFICATION_REQUEST')),
        (u'DISCLOSURE', 6, _(u'inforequests:Action:type:DISCLOSURE')),
        (u'REFUSAL', 7, _(u'inforequests:Action:type:REFUSAL')),
        (u'AFFIRMATION', 8, _(u'inforequests:Action:type:AFFIRMATION')),
        (u'REVERSION', 9, _(u'inforequests:Action:type:REVERSION')),
        (u'REMANDMENT', 10, _(u'inforequests:Action:type:REMANDMENT')),
        # Implicit actions
        (u'ADVANCED_REQUEST', 11,
         _(u'inforequests:Action:type:ADVANCED_REQUEST')),
        (u'EXPIRATION', 14, _(u'inforequests:Action:type:EXPIRATION')),
        (u'APPEAL_EXPIRATION', 15,
         _(u'inforequests:Action:type:APPEAL_EXPIRATION')),
    )
    APPLICANT_ACTION_TYPES = (
        TYPES.REQUEST,
        TYPES.CLARIFICATION_RESPONSE,
        TYPES.APPEAL,
    )
    APPLICANT_EMAIL_ACTION_TYPES = (
        TYPES.REQUEST,
        TYPES.CLARIFICATION_RESPONSE,
    )
    OBLIGEE_ACTION_TYPES = (
        TYPES.CONFIRMATION,
        TYPES.EXTENSION,
        TYPES.ADVANCEMENT,
        TYPES.CLARIFICATION_REQUEST,
        TYPES.DISCLOSURE,
        TYPES.REFUSAL,
        TYPES.AFFIRMATION,
        TYPES.REVERSION,
        TYPES.REMANDMENT,
    )
    OBLIGEE_EMAIL_ACTION_TYPES = (
        TYPES.CONFIRMATION,
        TYPES.EXTENSION,
        TYPES.ADVANCEMENT,
        TYPES.CLARIFICATION_REQUEST,
        TYPES.DISCLOSURE,
        TYPES.REFUSAL,
    )
    IMPLICIT_ACTION_TYPES = (
        TYPES.ADVANCED_REQUEST,
        TYPES.EXPIRATION,
        TYPES.APPEAL_EXPIRATION,
    )
    type = models.SmallIntegerField(choices=TYPES._choices)

    # May be empty for implicit actions; Should NOT be empty for other actions
    subject = models.CharField(blank=True, max_length=255)
    content = models.TextField(blank=True)

    # NOT NULL
    CONTENT_TYPES = FieldChoices(
        (u'PLAIN_TEXT', 1, u'Plain Text'),
        (u'HTML', 2, u'HTML'),
    )
    content_type = models.SmallIntegerField(choices=CONTENT_TYPES._choices,
                                            default=CONTENT_TYPES.PLAIN_TEXT,
                                            help_text=squeeze(u"""
                Mandatory choice action content type. Supported formats are plain text and html
                code. The html code is assumed to be safe. It is passed to the client without
                sanitizing. It must be sanitized before saving it here.
                """))

    # May be empty
    attachment_set = generic.GenericRelation(
        u'attachments.Attachment',
        content_type_field=u'generic_type',
        object_id_field=u'generic_id')

    # May NOT be NULL
    effective_date = models.DateField(help_text=squeeze(u"""
                The date at which the action was sent or received. If the action was sent/received
                by e‑mail it's set automatically. If it was sent/received by s‑mail it's filled by
                the applicant.
                """))

    # May be empty for obligee actions; Should be empty for other actions
    file_number = models.CharField(blank=True,
                                   max_length=255,
                                   help_text=squeeze(u"""
                A file number assigned to the action by the obligee. Usually only obligee actions
                have it. However, if we know tha obligee assigned a file number to an applicant
                action, we should keep it here as well. The file number is not mandatory.
                """))

    # May NOT be NULL for actions that set deadline; Must be NULL otherwise. Default value is
    # determined and automaticly set in save() when creating a new object. All actions that set
    # deadlines except CLARIFICATION_REQUEST, DISCLOSURE and REFUSAL set the deadline for the
    # obligee. CLARIFICATION_REQUEST, DISCLOSURE and REFUSAL set the deadline for the applicant.
    # DISCLOSURE sets the deadline only if not FULL.
    DEFAULT_DEADLINES = Bunch(
        # Applicant actions
        REQUEST=8,
        CLARIFICATION_RESPONSE=8,
        APPEAL=30,
        # Obligee actions
        CONFIRMATION=8,
        EXTENSION=10,
        ADVANCEMENT=30,
        CLARIFICATION_REQUEST=7,  # Deadline for the applicant
        DISCLOSURE=(
            lambda a: 15  # Deadline for the applicant if not full disclosure
            if a.disclosure_level != a.DISCLOSURE_LEVELS.FULL else None),
        REFUSAL=15,  # Deadline for the applicant
        AFFIRMATION=None,
        REVERSION=None,
        REMANDMENT=13,
        # Implicit actions
        ADVANCED_REQUEST=13,
        EXPIRATION=30,
        APPEAL_EXPIRATION=None,
    )
    SETTING_APPLICANT_DEADLINE_TYPES = (
        # Applicant actions
        # Obligee actions
        TYPES.ADVANCEMENT,
        TYPES.CLARIFICATION_REQUEST,
        TYPES.DISCLOSURE,
        TYPES.REFUSAL,
        # Implicit actions
        TYPES.EXPIRATION,
    )
    SETTING_OBLIGEE_DEADLINE_TYPES = (
        # Applicant actions
        TYPES.REQUEST,
        TYPES.CLARIFICATION_RESPONSE,
        TYPES.APPEAL,
        # Obligee actions
        TYPES.CONFIRMATION,
        TYPES.EXTENSION,
        TYPES.REMANDMENT,
        # Implicit actions
        TYPES.ADVANCED_REQUEST,
    )
    deadline = models.IntegerField(blank=True,
                                   null=True,
                                   help_text=squeeze(u"""
                The deadline that apply after the action, if the action sets a deadline, NULL
                otherwise. The deadline is expressed in a number of working days (WD) counting
                since the effective date. It may apply to the applicant or to the obligee,
                depending on the action type.
                """))

    # May be NULL
    extension = models.IntegerField(blank=True,
                                    null=True,
                                    help_text=squeeze(u"""
                Applicant extension to the deadline, if the action sets an obligee deadline. The
                applicant may extend the deadline after it is missed in order to be patient and
                wait a little longer. He may extend it multiple times. Applicant deadlines may not
                be extended.
                """))

    # NOT NULL for ADVANCEMENT, DISCLOSURE, REVERSION and REMANDMENT; NULL otherwise
    DISCLOSURE_LEVELS = FieldChoices(
        (u'NONE', 1, _(u'inforequests:Action:disclosure_level:NONE')),
        (u'PARTIAL', 2, _(u'inforequests:Action:disclosure_level:PARTIAL')),
        (u'FULL', 3, _(u'inforequests:Action:disclosure_level:FULL')),
    )
    disclosure_level = models.SmallIntegerField(
        choices=DISCLOSURE_LEVELS._choices,
        blank=True,
        null=True,
        help_text=squeeze(u"""
                Mandatory choice for advancement, disclosure, reversion and remandment actions,
                NULL otherwise. Specifies if the obligee disclosed any requested information by
                this action.
                """))

    # NOT NULL for REFUSAL and AFFIRMATION; NULL otherwise
    REFUSAL_REASONS = FieldChoices(
        (u'DOES_NOT_HAVE', u'3',
         _(u'inforequests:Action:refusal_reason:DOES_NOT_HAVE')),
        (u'DOES_NOT_PROVIDE', u'4',
         _(u'inforequests:Action:refusal_reason:DOES_NOT_PROVIDE')),
        (u'DOES_NOT_CREATE', u'5',
         _(u'inforequests:Action:refusal_reason:DOES_NOT_CREATE')),
        (u'COPYRIGHT', u'6',
         _(u'inforequests:Action:refusal_reason:COPYRIGHT')),
        (u'BUSINESS_SECRET', u'7',
         _(u'inforequests:Action:refusal_reason:BUSINESS_SECRET')),
        (u'PERSONAL', u'8', _(u'inforequests:Action:refusal_reason:PERSONAL')),
        (u'CONFIDENTIAL', u'9',
         _(u'inforequests:Action:refusal_reason:CONFIDENTIAL')),
        (u'OTHER_REASON', u'-2',
         _(u'inforequests:Action:refusal_reason:OTHER_REASON')),
    )
    refusal_reason = MultiSelectField(choices=REFUSAL_REASONS._choices,
                                      blank=True,
                                      help_text=squeeze(u"""
                Optional multichoice for refusal and affirmation actions, NULL otherwise. Specifies
                the reason why the obligee refused to disclose the information. Empty value
                means that the obligee refused to disclose it with no reason.
                """))

    # May be NULL; Used by ``cron.obligee_deadline_reminder`` and ``cron.applicant_deadline_reminder``
    last_deadline_reminder = models.DateTimeField(blank=True, null=True)

    # Backward relations:
    #
    #  -- advanced_to_set: by Branch.advanced_by
    #     May NOT be empty for ADVANCEMENT; Must be empty otherwise

    # Backward relations added to other models:
    #
    #  -- Branch.action_set
    #     Should NOT be empty
    #
    #  -- Message.action
    #     May be undefined

    objects = ActionQuerySet.as_manager()

    class Meta:
        index_together = [
            [u'effective_date', u'id'],
            # [u'branch'] -- ForeignKey defines index by default
            # [u'email'] -- OneToOneField defines index by default
        ]

    @staticmethod
    def prefetch_attachments(path=None, queryset=None):
        u"""
        Use to prefetch ``Action.attachments``.
        """
        if queryset is None:
            queryset = Attachment.objects.get_queryset()
        queryset = queryset.order_by_pk()
        return Prefetch(join_lookup(path, u'attachment_set'),
                        queryset,
                        to_attr=u'attachments')

    @cached_property
    def attachments(self):
        u"""
        Cached list of all action attachments ordered by ``pk``. May be prefetched with
        ``prefetch_related(Action.prefetch_attachments())`` queryset method.
        """
        return list(self.attachment_set.order_by_pk())

    @cached_property
    def is_applicant_action(self):
        return self.type in self.APPLICANT_ACTION_TYPES

    @cached_property
    def is_obligee_action(self):
        return self.type in self.OBLIGEE_ACTION_TYPES

    @cached_property
    def is_implicit_action(self):
        return self.type in self.IMPLICIT_ACTION_TYPES

    @cached_property
    def is_by_email(self):
        return self.email_id is not None

    @cached_property
    def days_passed(self):
        return self.days_passed_at(local_today())

    @cached_property
    def deadline_remaining(self):
        return self.deadline_remaining_at(local_today())

    @cached_property
    def deadline_missed(self):
        return self.deadline_missed_at(local_today())

    @cached_property
    def deadline_date(self):
        if self.deadline is None:
            return None
        deadline = self.deadline + (self.extension or 0)
        return workdays.advance(self.effective_date, deadline)

    @cached_property
    def has_deadline(self):
        return self.deadline is not None

    @cached_property
    def has_applicant_deadline(self):
        return self.deadline is not None and self.type in self.SETTING_APPLICANT_DEADLINE_TYPES

    @cached_property
    def has_obligee_deadline(self):
        return self.deadline is not None and self.type in self.SETTING_OBLIGEE_DEADLINE_TYPES

    @decorate(prevent_bulk_create=True)
    def save(self, *args, **kwargs):
        if self.pk is None:  # Creating a new object

            assert self.type is not None, u'%s.type is mandatory' % self.__class__.__name__
            if self.deadline is None:
                type_name = self.TYPES._inverse[self.type]
                deadline = getattr(self.DEFAULT_DEADLINES, type_name)
                self.deadline = deadline(self) if callable(
                    deadline) else deadline

        super(Action, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return self.branch.inforequest.get_absolute_url(u'#action-%d' %
                                                        self.pk)

    def days_passed_at(self, at):
        return workdays.between(self.effective_date, at)

    def deadline_remaining_at(self, at):
        if self.deadline is None:
            return None
        deadline = self.deadline + (self.extension or 0)
        return deadline - self.days_passed_at(at)

    def deadline_missed_at(self, at):
        # self.deadline_remaining is 0 on the last day of deadline
        remaining = self.deadline_remaining_at(at)
        return remaining is not None and remaining < 0

    def send_by_email(self):
        if not self.is_applicant_action:
            raise TypeError(u'%s is not applicant action' %
                            self.get_type_display())

        sender_name = self.branch.inforequest.applicant_name
        sender_address = self.branch.inforequest.unique_email
        sender_formatted = formataddr((squeeze(sender_name), sender_address))
        recipients = (formataddr(r)
                      for r in self.branch.collect_obligee_emails())

        # FIXME: Attachment name and content type are set by client and not to be trusted. The name
        # must be sanitized and the content type white listed for known content types. Any unknown
        # content type should be replaced with 'application/octet-stream'.

        msg = EmailMessage(self.subject, self.content, sender_formatted,
                           recipients)
        for attachment in self.attachments:
            msg.attach(attachment.name, attachment.content,
                       attachment.content_type)
        msg.send()

        inforequestemail = InforequestEmail(
            inforequest=self.branch.inforequest,
            email=msg.instance,
            type=InforequestEmail.TYPES.APPLICANT_ACTION,
        )
        inforequestemail.save()

        self.email = msg.instance
        self.save(update_fields=[u'email'])

    def __unicode__(self):
        return u'%s' % self.pk
Example #25
0
 def test_empty_bunch(self):
     bunch = Bunch()
     with self.assertRaises(AttributeError):
         bunch.first
Example #26
0
def paragraphs(context):
    context.push()
    context[u'_paragraph_status'] = Bunch(opened=False, after=0)
    return u''