def delete_vote(self, request, form): layout = DeletePageLayout(self, request) if form.submitted(request): request.session.delete(self) request.message(_("Page deleted"), 'success') return request.redirect(layout.homepage_url) return { 'layout': layout, 'form': form, 'subtitle': self.title, 'message': _('Do you really want to delete "${item}"?', mapping={'item': self.title}), 'button_text': _("Delete"), 'button_class': 'alert', 'cancel': request.link(self) }
def __call__(self, field, **kwargs): kwargs['class_'] = 'policy-selector' kwargs['data-tree'] = dumps(field.tree) kwargs['data-placehoder-text'] = field.gettext( _("Select Some Options")) kwargs['data-no-matches-text'] = field.gettext(_("No results match")) return super().__call__(field, **kwargs)
def breadcrumbs(self): return [ Link(_("Homepage"), self.homepage_url), Link(_("Votes"), self.votes_url), Link(self.model.short_title, self.request.link(self.model)), Link(self.title, '#'), ]
class PageForm(Form): title = StringField(label=_("Title"), validators=[InputRequired()]) content = QuillField(label=_("Content"), tags=('strong', 'em', 'a', 'h3', 'ol', 'ul', 'blockquote'), validators=[InputRequired()]) @property def id(self): """ An ID based on the title. """ id = normalize_for_url(self.title.data or 'page') query = self.request.session.query(TranslatablePage) while query.filter_by(id=id).first(): id = increment_name(id) return id def update_model(self, model): model.title = self.title.data model.content = self.content.data model.id = model.id or self.id def apply_model(self, model): self.title.data = model.title self.content.data = model.content
def update_votes(self, request, form): self = self.default() layout = UpdateVotesLayout(self, request) if form.submitted(request): added, updated = self.update(form.dataset.data) request.message( _("Dataset updated (${added} added, ${updated} updated)", mapping={ 'added': added, 'updated': updated }), 'success') # Warn if descriptor labels are missing missing = set() for vote in self.query(): for policy_area in vote.policy_areas: missing |= set(path for path in policy_area.label_path if not isinstance(path, TranslationString)) if missing: request.message( _("The dataset contains unknown descriptors: ${items}.", mapping={'items': ', '.join(sorted(missing))}), 'warning') return request.redirect(layout.votes_url) return { 'layout': layout, 'form': form, 'cancel': request.link(self), 'button_text': _("Update"), }
def delete_page_attachment(self, request, form): """ Delete an attachment. """ layout = DeletePageAttachmentLayout(self, request) if form.submitted(request): url = request.link(self.linked_swissvotes_page[0], 'attachments') request.session.delete(self) request.message(_("Attachment deleted."), 'success') return redirect(url) return { 'message': _('Do you really want to delete "${item}"?', mapping={'item': self.filename}), 'layout': layout, 'form': form, 'title': self.filename, 'subtitle': _("Delete"), 'button_text': _("Delete"), 'button_class': 'alert', 'cancel': request.link(self) }
def editbar_links(self): result = [] if self.request.has_role('admin', 'editor'): result.append( Link( text=_("Update dataset"), url=self.request.link(self.model.default(), name='update'), attrs={'class': 'upload-icon'} ) ) result.append( Link( text=_("Download dataset (CSV)"), url=self.request.link(self.model.default(), name='csv'), attrs={'class': 'export-icon'} ) ) result.append( Link( text=_("Download dataset (XLSX)"), url=self.request.link(self.model.default(), name='xlsx'), attrs={'class': 'export-icon'} ) ) if self.request.has_role('admin'): result.append( Link( text=_("Delete all votes"), url=self.request.link(self.model.default(), name='delete'), attrs={'class': 'delete-icon'} ) ) return result
def breadcrumbs(self): if self.model.id == 'home': return [Link(_("Homepage"), self.homepage_url)] return [ Link(_("Homepage"), self.homepage_url), Link(self.title, '#'), ]
class UpdateDatasetForm(Form): callout = _("Updating the dataset may take some time.") dataset = SwissvoteDatasetField( label=_("Dataset"), validators=[ DataRequired() ] )
def handle_notfound(self, request): """ Displays a nice HTTP 404 error. """ @request.after def set_status_code(response): response.status_code = self.code return { 'layout': DefaultLayout(self, request), 'title': _("Page not Found"), 'message': _("The page you are looking for could not be found."), }
def handle_forbidden(self, request): """ Displays a nice HTTP 403 error. """ @request.after def set_status_code(response): response.status_code = self.code return { 'layout': DefaultLayout(self, request), 'title': _("Access Denied"), 'message': _("You are trying to open a page for which you are not authorized.") }
def edit_page(self, request, form): request.include('quill') if form.submitted(request): form.update_model(self) request.message(_("Page modified."), 'success') return request.redirect(request.link(self)) if not form.errors: form.apply_model(self) return { 'layout': EditPageLayout(self, request), 'form': form, 'button_text': _("Update"), }
def test_layout_delete_vote(swissvotes_app): request = DummyRequest() request.app = swissvotes_app model = SwissVote( title_de="Vote", title_fr="Vote", short_title_de="Vote", short_title_fr="Vote", ) layout = DeleteVoteLayout(model, request) assert layout.title == _("Delete vote") assert layout.editbar_links == [] assert path( layout.breadcrumbs) == ('Principal/SwissVoteCollection/SwissVote/#') # Log in as editor request.roles = ['editor'] layout = DeleteVoteLayout(model, request) assert layout.editbar_links == [] # Log in as admin request.roles = ['admin'] layout = DeleteVoteLayout(model, request) assert layout.editbar_links == []
def test_layout_upload_vote_attachemts(swissvotes_app): request = DummyRequest() request.app = swissvotes_app model = SwissVote( title_de="Vote", title_fr="Vote", short_title_de="Vote", short_title_fr="Vote", ) layout = UploadVoteAttachemtsLayout(model, request) assert layout.title == _("Manage attachments") assert layout.editbar_links == [] assert path( layout.breadcrumbs) == ('Principal/SwissVoteCollection/SwissVote/#') # Log in as editor request.roles = ['editor'] layout = UploadVoteAttachemtsLayout(model, request) assert layout.editbar_links == [] # Log in as admin request.roles = ['admin'] layout = UploadVoteAttachemtsLayout(model, request) assert layout.editbar_links == []
def test_layout_votes(swissvotes_app): request = DummyRequest() request.app = swissvotes_app model = SwissVoteCollection(swissvotes_app) layout = VotesLayout(model, request) assert layout.title == _("Votes") assert layout.editbar_links == [] assert path(layout.breadcrumbs) == 'Principal/SwissVoteCollection' # Log in as editor request.roles = ['editor'] layout = VotesLayout(model, request) assert list(hrefs(layout.editbar_links)) == [ 'SwissVoteCollection/update', 'SwissVoteCollection/csv', 'SwissVoteCollection/xlsx' ] # Log in as admin request.roles = ['admin'] layout = VotesLayout(model, request) assert list(hrefs(layout.editbar_links)) == [ 'SwissVoteCollection/update', 'SwissVoteCollection/csv', 'SwissVoteCollection/xlsx', 'SwissVoteCollection/delete', ]
def editbar_links(self): result = [] if self.request.has_role('admin', 'editor'): result.append( Link( text=_("Manage attachments"), url=self.request.link(self.model, name='upload'), attrs={'class': 'upload-icon'} ) ) result.append( Link( text=_("Delete vote"), url=self.request.link(self.model, name='delete'), attrs={'class': 'delete-icon'} ) ) return result
def delete_votes(self, request, form): self = self.default() layout = DeleteVotesLayout(self, request) if form.submitted(request): for vote in self.query(): request.session.delete(vote) request.message(_("All votes deleted"), 'success') return request.redirect(layout.votes_url) return { 'layout': layout, 'form': form, 'message': _("Do you really want to delete all votes?!"), 'button_text': _("Delete"), 'button_class': 'alert', 'cancel': request.link(self) }
def top_navigation(self): result = [Link(_("Votes"), self.votes_url)] for page in self.pages.query(): if page.id not in self.request.app.static_content_pages: result.append( Link( page.title, self.request.link(page), sortable_id=page.id, )) return result
def add_page(self, request, form): request.include('quill') if form.submitted(request): page = TranslatablePage() form.update_model(page) request.session.add(page) request.message(_("Page added."), 'success') return request.redirect(request.link(page)) return {'layout': AddPageLayout(self, request), 'form': form}
def upload_vote_attachments(self, request, form): layout = UploadVoteAttachemtsLayout(self, request) if form.submitted(request): form.update_model(self) request.message(_("Attachments updated"), 'success') return request.redirect(request.link(self)) if not form.errors: form.apply_model(self) return {'layout': layout, 'form': form, 'cancel': request.link(self)}
def handle_password_reset_request(self, request, form): """ Handles the password reset requests. """ layout = DefaultLayout(self, request) 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)) message = _( 'A password reset link has been sent to ${email}, provided an ' 'account exists for this email address.', mapping={'email': form.email.data}) request.message(message, 'success') return request.redirect(layout.homepage_url) return { 'layout': layout, 'title': _('Reset password'), 'form': form, 'button_text': _("Submit"), }
def set_filename(response): bfs_number = self.linked_swissvotes[0].bfs_number name = self.name.split('-')[0] extension = {'results_by_domain': 'xlsx'}.get(name, 'pdf') title = { 'voting_text': _("Voting text"), 'brief_description': _("Brief description Swissvotes"), 'realization': _("Realization"), 'federal_council_message': _("Federal council message"), 'parliamentary_debate': _("Parliamentary debate"), 'voting_booklet': _("Voting booklet"), 'ad_analysis': _("Analysis of the advertising campaign"), 'resolution': _("Resolution"), 'results_by_domain': _("Result by canton, district and municipality") }.get(name, '') title = normalize_for_url(request.translate(title)) response.headers['Content-Disposition'] = ( f'inline; filename={bfs_number}-{title}.{extension}')
def editbar_links(self): result = [] if self.request.has_role('admin', 'editor'): result.append( Link( text=_("Edit page"), url=self.request.link(self.model, name='edit'), attrs={'class': 'edit-icon'} ) ) result.append( Link( text=_("Manage attachments"), url=self.request.link(self.model, name='attachments'), attrs={'class': 'upload-icon'} ) ) if self.model.id not in self.app.static_content_pages: result.append( Link( text=_("Delete page"), url=self.request.link(self.model, name='delete'), attrs={'class': 'delete-icon'} ) ) result.append( LinkGroup( title=_("Add"), links=[ Link( text=_("Page"), url=self.request.link(self.pages, name='add'), attrs={'class': 'page-icon'} ) ] ), ) return result
def handle_login(self, request, form): """ Handles the login requests. """ layout = DefaultLayout(self, request) if form.submitted(request): self.to = relative_url(layout.homepage_url) 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(Auth.from_request(request), name='request-password'), 'button_text': _("Submit"), }
def handle_password_reset(self, request, form): """ Handles password reset requests. """ layout = DefaultLayout(self, request) if form.submitted(request): if form.update_password(request): request.message(_("Password changed."), 'success') return request.redirect(layout.login_url) 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, 'button_text': _("Submit"), }
def view_page_attachments(self, request): """ View all attachments of a page and allow to drop new attachments. """ layout = PageAttachmentsLayout(self, request) upload_url = layout.csrf_protected_url(request.link(self, name='upload')) files = [file for file in self.files if file.locale == request.locale] return { 'layout': layout, 'title': self.title, 'subtitle': _("Attachments"), 'upload_url': upload_url, 'files': files, 'notice': self, }
def test_layout_delete_votes(swissvotes_app): request = DummyRequest() request.app = swissvotes_app model = SwissVoteCollection(swissvotes_app) layout = DeleteVotesLayout(model, request) assert layout.title == _("Delete all votes") assert layout.editbar_links == [] assert path(layout.breadcrumbs) == 'Principal/SwissVoteCollection/#' # Log in as editor request.roles = ['editor'] layout = DeleteVotesLayout(model, request) assert layout.editbar_links == [] # Log in as admin request.roles = ['admin'] layout = DeleteVotesLayout(model, request) assert layout.editbar_links == []
def upload_page_attachment(self, request): """ Upload an attachment and add it to the page. Raises a HTTP 415 (Unsupported Media Type) if the file format is not supported. """ request.assert_valid_csrf_token() attachment = TranslatablePageFile(id=random_token()) attachment.name = '{}-{}'.format(request.locale, request.params['file'].filename) attachment.reference = as_fileintent(request.params['file'].file, request.params['file'].filename) self.files.append(attachment) request.message(_("Attachment added."), 'success') return redirect(request.link(self, 'attachments'))
def codes(attribute): """ Returns the codes for the given attribute as defined in the code book. """ if attribute == 'legal_form': return OrderedDict(( (1, _("Mandatory referendum")), (2, _("Optional referendum")), (3, _("Popular initiative")), (4, _("Direct counter-proposal")), )) if attribute == 'result_cantons_accepted': return OrderedDict(( (0, _("Rejected")), (1, _("Accepted")), (3, _("Majority of the cantons not necessary")), )) if attribute == 'result' or attribute.endswith('_accepted'): return OrderedDict(( (0, _("Rejected")), (1, _("Accepted")), )) if attribute == 'department_in_charge': return OrderedDict(( (1, _("Federal Department of Foreign Affairs (FDFA)")), (2, _("Federal Department of Home Affairs (FDHA)")), (3, _("Federal Department of Justice and Police (FDJP)")), (4, _("Federal Department of Defence, Civil Protection and " "Sport (DDPS)")), (5, _("Federal Department of Finance (FDF)")), (6, _("Federal Department of Economic Affairs, Education and " "Research (EAER)")), (7, _("Federal Department of the Environment, Transport, " "Energy and Communications (DETEC)")), (8, _("Federal Chancellery (FCh)")), )) if (attribute == 'position_federal_council' or attribute == 'position_national_council' or attribute == 'position_council_of_states'): return OrderedDict( ((1, _("Accepting")), (2, _("Rejecting")), (3, _("None")))) if attribute == 'position_parliament': return OrderedDict(( (1, _("Accepting")), (2, _("Rejecting")), )) if attribute == 'recommendation': # Added ordering how it should be displayed in strengths table return OrderedDict( ((1, _("Yea")), (2, _("Nay")), (4, _("Empty")), (5, _("Free vote")), (3, _("None")), (66, _("Neutral")), (9999, _("Organization no longer exists")), (None, _("unknown")))) raise RuntimeError(f"No codes available for '{attribute}'")
def post_validate(self, form, validation_stopped): """ Make sure the given XLSX is valid (all expected columns are present all cells contain reasonable values). Converts the XLSX to a list of SwissVote objects, available as ``data``. """ super(SwissvoteDatasetField, self).post_validate(form, validation_stopped) if validation_stopped: return errors = [] data = [] mapper = ColumnMapper() try: workbook = open_workbook( file_contents=self.raw_data[0].file.read()) except Exception: raise ValueError(_("Not a valid XLSX file.")) if workbook.nsheets < 1: raise ValueError(_("No data.")) data_sheet_name = 'DATA' citation_sheet_name = 'CITATION' if data_sheet_name not in workbook.sheet_names(): raise ValueError(_('Sheet DATA is missing.')) if citation_sheet_name not in workbook.sheet_names(): raise ValueError(_('Sheet CITATION is missing.')) sheet = workbook.sheet_by_name(data_sheet_name) if sheet.nrows <= 1: raise ValueError(_("No data.")) headers = [column.value for column in sheet.row(0)] missing = set(mapper.columns.values()) - set(headers) if missing: raise ValueError( _("Some columns are missing: ${columns}.", mapping={'columns': ', '.join(missing)})) for index in range(1, sheet.nrows): row = sheet.row(index) vote = SwissVote() for (attribute, column, type_, nullable, precision, scale) in mapper.items(): cell = row[headers.index(column)] try: if cell.ctype == XL_CELL_EMPTY: value = None elif type_ == 'TEXT': value = str(cell.value) value = '' if value == '.' else value elif type_ == 'DATE': if isinstance(cell.value, str): value = parse(cell.value, dayfirst=True).date() else: value = xldate.xldate_as_datetime( cell.value, workbook.datemode).date() elif type_ == 'INTEGER': if isinstance(cell.value, str): value = cell.value value = '' if value == '.' else value value = int(value) if value else None else: value = int(cell.value) elif type_ == 'INT4RANGE': value = NumericRange( *[int(bound) for bound in cell.value.split('-')]) elif type_.startswith('NUMERIC'): if isinstance(cell.value, str): value = cell.value value = '' if value == '.' else value value = Decimal(str(value)) if value else None else: value = Decimal(str(cell.value)) if value is not None: value = Decimal( format(value, f'{precision}.{scale}f')) except Exception: errors.append( (index, column, f"'{value}' ≠ {type_.lower()}")) else: if not nullable and value is None: errors.append((index, column, "∅")) mapper.set_value(vote, attribute, value) data.append(vote) if errors: raise ValueError( _("Some cells contain invalid values: ${errors}.", mapping={ 'errors': '; '.join( ['{}:{} {}'.format(*error) for error in errors]) })) self.data = data