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 }
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 }
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 }
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>' ]
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 }
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}
def preview_notice(self, request): """ Preview the notice. """ layout = Layout(self, request) return { 'layout': layout, 'notice': self, 'export': request.link(self, name='preview-pdf') }
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/'
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 }
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."), }
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 }
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) }
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() }
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." ) }
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') }
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) }
def test_layout_format(session, principal): request = DummyRequest(session, principal) layout = Layout(None, request) # Date assert layout.principal.time_zone == 'Europe/Zurich' assert layout.format_date(date(2019, 1, 2), 'date') == '02.01.2019' assert layout.format_date(datetime(2019, 1, 2, 12), 'date') == '02.01.2019' assert layout.format_date(datetime(2019, 1, 2, 12), 'date_long') == \ '2. Januar 2019' assert layout.format_date(datetime(2019, 1, 2, 12), 'datetime') == \ '02.01.2019 12:00' assert layout.format_date( standardize_date(datetime(2019, 1, 2, 12, 0), 'UTC'), 'date') == '02.01.2019' assert layout.format_date( standardize_date(datetime(2019, 1, 2, 12, 0), 'UTC'), 'datetime') == '02.01.2019 13:00' # Issue with raises(AssertionError): layout.format_issue(None) with raises(AssertionError): layout.format_issue('') assert layout.format_issue(Issue()) == 'No. , ' assert layout.format_issue(Issue(number=1, date=date(2017, 1, 2))) == 'No. 1, 02.01.2017' assert layout.format_issue( Issue(number=1, date=date(2017, 1, 2)), date_format='date_with_weekday') == 'No. 1, Montag 02.01.2017' assert layout.format_issue(Issue(name='2017-1', number=1, date=date(2017, 1, 2)), notice=GazetteNotice()) == 'No. 1, 02.01.2017' assert layout.format_issue( Issue(name='2017-1', number=1, date=date(2017, 1, 2)), notice=GazetteNotice(issues=['2017-1'])) == 'No. 1, 02.01.2017' assert layout.format_issue( Issue(name='2017-1', number=1, date=date(2017, 1, 2)), notice=GazetteNotice( _issues={'2017-1': 10})) == 'No. 1, 02.01.2017 / 10' # Text assert layout.format_text(None) == '' assert layout.format_text('abc') == 'abc' assert layout.format_text('a\nb\r\nc') == 'a<br>b<br>c'
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
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') }
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')
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 }
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') }
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 }
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 }
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) }
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' ), }
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 }
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 }
def view_principal(self, request): """ The homepage. Redirects to the default management views according to the logged in role. Shows the weekly PDFs if not logged-in. """ layout = Layout(self, request) if request.is_private(self): return redirect(layout.manage_notices_link) if request.is_personal(self): return redirect(layout.dashboard_link) if not request.app.principal.frontend: return redirect(layout.login_link) return redirect(request.link(self, name='archive'))
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'), }