예제 #1
0
    def on_request(self):
        session = self.request.session

        # populate organization (active root elements with no children or
        # active children (but not their parents))
        self.organization.choices = []
        self.organization.choices.append(
            ('', self.request.translate(_("Select one"))))
        query = session.query(Organization)
        query = query.filter(Organization.active.is_(True))
        query = query.filter(Organization.parent_id.is_(None))
        query = query.order_by(Organization.order)
        for root in query:
            if root.children:
                for child in root.children:
                    if child.active:
                        self.organization.choices.append(
                            (child.name, child.title))
            else:
                self.organization.choices.append((root.name, root.title))

        # populate categories
        query = session.query(Category.name, Category.title)
        query = query.filter(Category.active.is_(True))
        query = query.order_by(Category.order)
        self.category.choices = query.all()

        # populate issues
        now = utcnow()
        layout = Layout(None, self.request)

        self.issues.choices = []
        query = session.query(Issue)
        query = query.order_by(Issue.date)
        if self.request.is_private(self.model):
            query = query.filter(date.today() < Issue.date)  # publisher
        else:
            query = query.filter(now < Issue.deadline)  # editor
        for issue in query:
            self.issues.choices.append(
                (issue.name,
                 layout.format_issue(issue, date_format='date_with_weekday')))
            if now >= issue.deadline:
                self.issues.render_kw['data-hot-issue'] = issue.name

        # Remove the print only option if not publisher
        if not self.request.is_private(self.model):
            self.delete_field('print_only')
예제 #2
0
def edit_user(self, request, form):
    """ Edit the role, name and email of a user.

    Publishers may only edit members. Admins can not be edited.

    """

    layout = Layout(self, request)

    if self.role != 'member' and not request.is_secret(self):
        raise HTTPForbidden()

    if form.submitted(request):
        form.update_model(self)
        self.logout_all_sessions(request)
        request.message(_("User modified."), 'success')
        return redirect(layout.manage_users_link)

    if not form.errors:
        form.apply_model(self)

    return {
        'layout': layout,
        'form': form,
        'title': self.title,
        'subtitle': _("Edit User"),
        'cancel': layout.manage_users_link
    }
예제 #3
0
def handle_password_reset(self, request, form):

    layout = Layout(self, request)
    callout = None
    show_form = True
    if form.submitted(request):
        if form.update_password(request):
            show_form = False
            request.message(_("Password changed."), 'success')
            return redirect(layout.homepage_link)
        else:
            form.error_message = _(
                "Wrong username or password reset link not valid any more."
            )
            log.info(
                "Failed password reset attempt by {}".format(
                    request.client_addr
                )
            )

    if 'token' in request.params:
        form.token.data = request.params['token']

    return {
        'layout': layout,
        'title': _('Reset password'),
        'form': form,
        'show_form': show_form,
        'callout': callout
    }
예제 #4
0
def view_notices_update(self, request, form):
    """ Updates all notices (of this state): Applies the categories, issues and
    organization from the meta informations. This view is not used normally
    and only intended when changing category names in the principal definition,
    for example.

    """

    layout = Layout(self, request)
    session = request.session

    if form.submitted(request):
        for notice in self.query():
            notice.apply_meta(session)
        request.message(_("Notices updated."), 'success')

        return redirect(layout.dashboard_or_notices_link)

    return {
        'layout': layout,
        'form': form,
        'title': _("Update notices"),
        'button_text': _("Update"),
        'cancel': layout.dashboard_or_notices_link
    }
예제 #5
0
def test_issues_pdf_notice(gazette_app):
    session = gazette_app.session()

    with freeze_time("2017-01-01 12:00"):
        notice = GazetteNotice(title='title',
                               text='text',
                               author_place='place',
                               author_date=utcnow(),
                               author_name='author',
                               state='drafted')
        notice.files.append(pdf_attachment('attachment'))
        session.add(notice)
        session.flush()

    layout = Layout(notice, DummyRequest(session, gazette_app.principal))

    pdf = IssuePdf(BytesIO())
    pdf.init_a4_portrait()

    pdf.notice(notice, layout, '1')
    assert extract_pdf_story(pdf) == [
        '1', 'title', 'text', 'place, 1. Januar 2017<br/>author'
    ]

    notice.print_only = True
    pdf.notice(notice, layout, '2')
    assert extract_pdf_story(pdf) == [
        '1', 'title', 'text', 'place, 1. Januar 2017<br/>author', '2',
        '<i>This official notice is only available in the print version.</i>'
    ]
예제 #6
0
def create_notice(self, request, form):
    """ Create a new notice.

    If a valid UID of a notice is given (via 'source' query parameter), its
    values are pre-filled in the form.

    This view is mainly used by the editors.

    """

    layout = Layout(self, request)
    user = get_user(request)

    source = None
    if self.source:
        source = self.query().filter(GazetteNotice.id == self.source)
        source = source.first()

    if form.submitted(request):
        notice = self.add(
            title=form.title.data,
            text=form.text.data,
            author_place=form.author_place.data,
            author_date=form.author_date_utc,
            author_name=form.author_name.data,
            organization_id=form.organization.data,
            category_id=form.category.data,
            print_only=form.print_only.data if form.print_only else False,
            at_cost=form.at_cost.data == 'yes',
            billing_address=form.billing_address.data,
            user=get_user(request),
            issues=form.issues.data
        )
        if form.phone_number.data:
            user.phone_number = form.phone_number.data
        if source:
            notice.note = source.note
        return redirect(request.link(notice))

    if not form.errors:
        if source:
            form.apply_model(source)
            if form.print_only:
                form.print_only.data = False

        form.phone_number.data = user.phone_number

    return {
        'layout': layout,
        'form': form,
        'title': _("New Official Notice"),
        'helptext': _(
            "The fields marked with an asterisk * are mandatory fields."
        ),
        'button_text': _("Save"),
        'cancel': layout.dashboard_or_notices_link,
        'current_issue': layout.current_issue
    }
예제 #7
0
def view_organizations_order(self, request):
    """ Reorder the list of organizations.

    This view is only visible by a publisher.

    """
    layout = Layout(self, request)
    roots = self.query().filter(Organization.parent_id.is_(None))

    return {'title': _("Organizations"), 'layout': layout, 'roots': roots}
예제 #8
0
def preview_notice(self, request):
    """ Preview the notice. """

    layout = Layout(self, request)

    return {
        'layout': layout,
        'notice': self,
        'export': request.link(self, name='preview-pdf')
    }
예제 #9
0
def handle_password_reset_request(self, request, form):
    """ Handles the password reset requests. """

    show_form = True
    callout = None

    if form.submitted(request):
        users = UserCollection(request.session)
        user = users.by_username(form.email.data)
        if user:
            url = password_reset_url(
                user,
                request,
                request.link(self, name='reset-password')
            )

            request.app.send_transactional_email(
                subject=request.translate(_("Password reset")),
                receivers=(user.username, ),
                reply_to=request.app.mail['transactional']['sender'],
                content=render_template(
                    'mail_password_reset.pt',
                    request,
                    {
                        'title': request.translate(_("Password reset")),
                        'model': None,
                        'url': url,
                        'layout': MailLayout(self, request)
                    }
                )
            )
        else:
            log.info(
                "Failed password reset attempt by {}".format(
                    request.client_addr
                )
            )

        show_form = False
        callout = _(
            (
                'A password reset link has been sent to ${email}, provided an '
                'account exists for this email address.'
            ),
            mapping={'email': form.email.data}
        )

    return {
        'layout': Layout(self, request),
        'title': _('Reset password'),
        'form': form,
        'show_form': show_form,
        'callout': callout
    }
예제 #10
0
def handle_notfound(self, request):

    @request.after
    def set_status_code(response):
        response.status_code = self.code  # pass along 404

    return {
        'layout': Layout(self, request),
        'title': _("Page not Found"),
        'message': _("The page you are looking for could not be found."),
    }
예제 #11
0
def test_layout_links():
    layout = Layout(None, DummyRequest(None))
    assert layout.homepage_link == '/'
    assert layout.manage_users_link == '/UserCollection/'
    assert layout.manage_groups_link == '/UserGroupCollection/'
    assert layout.manage_organizations_link == '/OrganizationCollection/'
    assert layout.manage_categories_link == '/CategoryCollection/'
    assert layout.manage_issues_link == '/IssueCollection/'
    assert layout.manage_notices_link == '/GazetteNoticeCollection/'
    assert layout.dashboard_link == '/dashboard/'
    assert layout.dashboard_or_notices_link == '/dashboard/'
예제 #12
0
def view_dashboard(self, request):
    """ The dashboard view (for editors).

    Shows the drafted, submitted and rejected notices, shows warnings and
    allows to create a new notice.

    """
    layout = Layout(self, request)

    user_ids, group_ids = get_user_and_group(request)
    collection = GazetteNoticeCollection(request.session,
                                         user_ids=user_ids,
                                         group_ids=group_ids)

    # rejected
    rejected = collection.for_state('rejected').query().all()
    if rejected:
        request.message(_("You have rejected messages."), 'warning')

    # drafted
    drafted = collection.for_state('drafted').query().all()
    now = utcnow()
    limit = now + timedelta(days=2)
    past_issues_selected = False
    deadline_reached_soon = False
    for notice in drafted:
        for issue in notice.issue_objects:
            if issue.deadline < now:
                past_issues_selected = True
            elif issue.deadline < limit:
                deadline_reached_soon = True
    if past_issues_selected:
        request.message(_("You have drafted messages with past issues."),
                        'warning')
    if deadline_reached_soon:
        request.message(
            _("You have drafted messages with issues close to the deadline."),
            'warning')

    # submitted
    submitted = collection.for_state('submitted').query().all()

    new_notice = request.link(collection.for_state('drafted'),
                              name='new-notice')

    return {
        'layout': layout,
        'title': _("Dashboard"),
        'rejected': rejected,
        'drafted': drafted,
        'submitted': submitted,
        'new_notice': new_notice,
        'current_issue': layout.current_issue
    }
예제 #13
0
def view_archive(self, request):
    """ The archive.

    Shows all the weekly PDFs by year.

    """
    layout = Layout(self, request)

    return {
        'layout': layout,
        'issues': IssueCollection(request.session).by_years(desc=True)
    }
예제 #14
0
def handle_forbidden(self, request):

    @request.after
    def set_status_code(response):
        response.status_code = self.code  # pass along 403

    return {
        'layout': Layout(self, request),
        'title': _("Access Denied"),
        'message': _(
            "You are trying to open a page for which you are not authorized."
        )
    }
예제 #15
0
    def on_request(self):
        session = self.request.session
        layout = Layout(None, self.request)

        def title(item):
            return item.title if item.active else '({})'.format(item.title)

        # populate organization (root elements with no children or children
        # (but not their parents))
        self.organization.choices = []
        self.organization.choices.append(
            ('', self.request.translate(_("Select one"))))
        query = session.query(Organization)
        query = query.filter(Organization.parent_id.is_(None))
        query = query.order_by(Organization.order)
        for root in query:
            if root.children:
                for child in root.children:
                    self.organization.choices.append(
                        (child.name, title(child)))
            else:
                self.organization.choices.append((root.name, title(root)))

        # populate categories
        self.category.choices = []
        query = session.query(Category)
        query = query.order_by(Category.order)
        for category in query:
            self.category.choices.append((category.name, title(category)))

        # populate issues
        del self.issues.render_kw['data-limit']
        self.issues.choices = []
        query = session.query(Issue)
        query = query.order_by(Issue.date)
        for issue in query:
            self.issues.choices.append(
                (issue.name,
                 layout.format_issue(issue, date_format='date_with_weekday')))
예제 #16
0
def view_user_sessions(self, request):
    """ View all open browser sessions.

    This view is only visible by an admin.

    """

    layout = Layout(self, request)
    return {
        'layout': layout,
        'title': _('Sessions'),
        'users': self.query().all()
    }
예제 #17
0
def accept_notice(self, request, form):
    """ Accept a notice.

    This view is used by the publishers to accept a submitted notice.

    Only submitted notices may be accepted.

    """

    layout = Layout(self, request)

    if self.state != 'submitted' and self.state != 'imported':
        return {
            'layout': layout,
            'title': self.title,
            'subtitle': _("Accept Official Note"),
            'callout': _("Only submitted official notices may be accepted."),
            'show_form': False
        }

    if (self.state == 'submitted'
            and (self.expired_issues or self.invalid_category
                 or self.invalid_organization)):
        return redirect(request.link(self, name='edit'))

    if form.submitted(request):
        self.accept(request)
        request.message(_("Official notice accepted."), 'success')
        if request.app.principal.on_accept and self.state != 'imported':
            send_accepted_mail(request, self)
            self.add_change(request, _("mail sent"))
        return redirect(layout.dashboard_or_notices_link)

    return {
        'message':
        _('Do you really want to accept "${item}"?',
          mapping={'item': self.title}),
        'layout':
        layout,
        'form':
        form,
        'title':
        self.title,
        'subtitle':
        _("Accept Official Note"),
        'button_text':
        _("Accept Official Note"),
        'cancel':
        request.link(self)
    }
예제 #18
0
def view_groups(self, request):
    """ View all the user groups.

    This view is only visible by an admin.

    """

    layout = Layout(self, request)
    return {
        'layout': layout,
        'groups': self.query().all(),
        'title': _('Groups'),
        'new_group': request.link(self, name='new-group')
    }
예제 #19
0
def view_categories(self, request):
    """ View the list of categories.

    This view is only visible by an admin.

    """
    layout = Layout(self, request)

    return {
        'title': _("Categories"),
        'layout': layout,
        'categories': self.query().all(),
        'export': request.link(self, name='export'),
        'new_category': request.link(self, name='new-category')
    }
예제 #20
0
    def from_notice(cls, notice, request):
        """ Create a PDF from a single notice. """

        layout = Layout(None, request)
        result = BytesIO()
        pdf = cls(result, author=request.app.principal.name)
        pdf.init_a4_portrait(page_fn=page_fn_footer,
                             page_fn_later=page_fn_header_and_footer)

        pdf.spacer()
        pdf.notice(notice, layout)
        pdf.generate()

        result.seek(0)
        return result
예제 #21
0
def view_notice_attachments(self, request):
    """ View all attachments to a single notice and allow to drop new
    attachments.

    Silently redirects to the notice view if the notice has already been
    accepted for non-admins.

    """

    layout = Layout(self, request)
    upload_url = layout.csrf_protected_url(request.link(self, name='upload'))

    if self.state == 'accepted' or self.state == 'published':
        if not request.is_secret(self):
            return redirect(request.link(self))

    return {
        'layout': layout,
        'title': self.title,
        'subtitle': _("Attachments"),
        'upload_url': upload_url,
        'files': self.files,
        'notice': self,
    }
예제 #22
0
def delete_group(self, request, form):
    """ Delete a user group.

    This view is only visible by an admin.

    """

    layout = Layout(self, request)

    if self.official_notices:
        request.message(_("There are official notices linked to this group!"),
                        'warning')

    if self.users.count():
        request.message(_('Only groups without users may be deleted.'),
                        'alert')
        return {
            'layout': layout,
            'title': self.name,
            'subtitle': _("Delete Group"),
            'show_form': False
        }

    if form.submitted(request):
        UserGroupCollection(request.session).delete(self)
        request.message(_("Group deleted."), 'success')
        return redirect(layout.manage_groups_link)

    return {
        'message':
        _('Do you really want to delete "${item}"?',
          mapping={'item': self.name}),
        'layout':
        layout,
        'form':
        form,
        'title':
        self.name,
        'subtitle':
        _("Delete Group"),
        'button_text':
        _("Delete Group"),
        'button_class':
        'alert',
        'cancel':
        layout.manage_groups_link
    }
예제 #23
0
def view_organizations(self, request):
    """ View the list of organizations.

    This view is only visible by a publisher.

    """
    layout = Layout(self, request)
    roots = self.query().filter(Organization.parent_id.is_(None))

    return {
        'title': _("Organizations"),
        'layout': layout,
        'roots': roots,
        'export': request.link(self, name='export'),
        'new_organization': request.link(self, name='new-organization'),
        'order': request.link(self, name='order')
    }
예제 #24
0
def delete_organization(self, request, form):
    """ Delete a organization.

    Only unused organizations may be deleted.

    """
    layout = Layout(self, request)
    session = request.session

    if self.children or self.in_use:
        request.message(
            _("Only unused organizations with no sub-organisations may be "
              "deleted."), 'alert')
        return {
            'layout': layout,
            'title': self.title,
            'subtitle': _("Delete Organization"),
            'show_form': False
        }

    if form.submitted(request):
        collection = OrganizationCollection(session)
        collection.delete(self)
        request.message(_("Organization deleted."), 'success')
        return redirect(layout.manage_organizations_link)

    return {
        'message':
        _('Do you really want to delete "${item}"?',
          mapping={'item': self.title}),
        'layout':
        layout,
        'form':
        form,
        'title':
        self.title,
        'subtitle':
        _("Delete Organization"),
        'button_text':
        _("Delete Organization"),
        'button_class':
        'alert',
        'cancel':
        layout.manage_organizations_link
    }
예제 #25
0
def delete_attachment(self, request, form):
    """ Delete a notice attachment. """

    layout = Layout(self, request)

    notice = self.linked_official_notices[0]
    if notice.state == 'accepted' or notice.state == 'published':
        if not request.is_secret(self):
            request.message(
                _("Attachments of accepted notices can not be deleted."),
                'alert')
            return {
                'layout': layout,
                'title': self.name,
                'subtitle': _("Delete"),
                'show_form': False
            }

    if form.submitted(request):
        url = request.link(self.linked_official_notices[0], 'attachments')
        request.session.delete(self)
        request.message(_("Attachment deleted."), 'success')
        notice.add_change(request, _("Attachment deleted."))
        return redirect(url)

    return {
        'message':
        _('Do you really want to delete "${item}"?',
          mapping={'item': self.name}),
        'layout':
        layout,
        'form':
        form,
        'title':
        self.name,
        'subtitle':
        _("Delete"),
        'button_text':
        _("Delete"),
        'button_class':
        'alert',
        'cancel':
        request.link(self)
    }
예제 #26
0
def delete_user(self, request, form):
    """ Delete a user.

    Publishers may only edit members. Admins can not be deleted.

    """

    layout = Layout(self, request)

    if self.role != 'member' and not request.is_secret(self):
        raise HTTPForbidden()

    if self.official_notices or self.changes:
        request.message(_("There are official notices linked to this user!"),
                        'warning')

    if form.submitted(request):
        collection = UserCollection(request.session)
        user = collection.by_username(self.username)
        if user.role != 'admin':
            self.logout_all_sessions(request)
            collection.delete(self.username)
            request.message(_("User deleted."), 'success')
        return redirect(layout.manage_users_link)

    return {
        'message':
        _('Do you really want to delete "${item}"?',
          mapping={'item': self.title}),
        'layout':
        layout,
        'form':
        form,
        'title':
        self.title,
        'subtitle':
        _("Delete User"),
        'button_text':
        _("Delete User"),
        'button_class':
        'alert',
        'cancel':
        layout.manage_users_link
    }
예제 #27
0
def handle_login(self, request, form):
    """ Handles the login requests. """

    layout = Layout(self, request)

    if form.submitted(request):
        response = self.login_to(request=request, **form.login_data)
        form.error_message = _("Wrong username or password")
    else:
        response = None

    return response or {
        'layout': layout,
        'title': _("Login"),
        'form': form,
        'password_reset_link': request.link(
            request.app.principal, name='request-password'
        ),
    }
예제 #28
0
def delete_issue(self, request, form):
    """ Delete a issue.

    Only unused issues may be deleted.

    This view is only visible by a publisher.

    """
    layout = Layout(self, request)
    session = request.session

    if self.in_use:
        request.message(
            _("Only unused issues may be deleted."),
            'alert'
        )
        return {
            'layout': layout,
            'title': self.name,
            'subtitle': _("Delete Issue"),
            'show_form': False
        }

    if form.submitted(request):
        collection = IssueCollection(session)
        collection.delete(self)
        request.message(_("Issue deleted."), 'success')
        return redirect(layout.manage_issues_link)

    return {
        'message': _(
            'Do you really want to delete "${item}"?',
            mapping={'item': self.name}
        ),
        'layout': layout,
        'form': form,
        'title': self.name,
        'subtitle': _("Delete Issue"),
        'button_text': _("Delete Issue"),
        'button_class': 'alert',
        'cancel': layout.manage_issues_link
    }
예제 #29
0
def create_group(self, request, form):
    """ Create a new user group.

    This view is only visible by an admin.

    """

    layout = Layout(self, request)

    if form.submitted(request):
        self.add(name=form.name.data)
        request.message(_("Group added."), 'success')
        return redirect(layout.manage_groups_link)

    return {
        'layout': layout,
        'form': form,
        'title': _("New Group"),
        'cancel': layout.manage_groups_link
    }
예제 #30
0
def view_issues(self, request):
    """ View the list of issues.

    This view is only visible by a publisher.

    """
    layout = Layout(self, request)

    today = date.today()
    past_issues = self.query().filter(Issue.date < today)
    past_issues = past_issues.order_by(None).order_by(Issue.date.desc())
    next_issues = self.query().filter(Issue.date >= today)

    return {
        'title': _("Issues"),
        'layout': layout,
        'past_issues': past_issues,
        'next_issues': next_issues,
        'new_issue': request.link(self, name='new-issue'),
        'export': request.link(self, name='export'),
    }