\n"
"Language-Team: German\n"
@@ -33,8 +33,12 @@ msgid "Show less"
msgstr "Weniger anzeigen"
#, python-format
-msgid "No. ${number}, ${issue_date}"
-msgstr "Nr. ${number}, ${issue_date}"
+msgid "No. ${issue_number}, ${issue_date} / ${notice_number}"
+msgstr "Nr. ${issue_number}, ${issue_date} / ${notice_number}"
+
+#, python-format
+msgid "No. ${issue_number}, ${issue_date}"
+msgstr "Nr. ${issue_number}, ${issue_date}"
msgid "Official Notices"
msgstr "Meldungen"
@@ -60,8 +64,11 @@ msgstr "Gruppen"
msgid "My Drafted and Submitted Official Notices"
msgstr "Offene Meldungen"
-msgid "My Accepted Official Notices"
-msgstr "Angenommene Meldungen"
+msgid "My Published Official Notices"
+msgstr "Veröffentlichte Meldungen"
+
+msgid "Gazette"
+msgstr "Amtsblatt"
msgid "This value already exists."
msgstr "Dieser Wert ist bereits vorhanden."
@@ -217,18 +224,24 @@ msgstr "Kommende Ausgaben"
msgid "Issue"
msgstr "Ausgabe"
+msgid "PDF"
+msgstr "PDF"
+
msgid "No upcoming issues."
msgstr "Keine kommenden Ausgaben."
+msgid "Publish"
+msgstr "Veröffentlichen"
+
+msgid "Generate"
+msgstr "Erzeugen"
+
msgid "Past Issues"
msgstr "Vergangene Ausgaben"
msgid "No past issues."
msgstr "Keine vergangenen Ausgaben."
-msgid "Gazette"
-msgstr "Amtsblatt"
-
msgid "homepage"
msgstr "Startseite"
@@ -472,6 +485,20 @@ msgstr "Ausgabe bearbeiten"
msgid "Delete Issue"
msgstr "Ausgabe löschen"
+msgid "Publish all notices"
+msgstr "Alle Meldungen veröffentlichen"
+
+#, python-format
+msgid ""
+"Do you really want to publish all notices of \"${item}\"? This will assign "
+"the publication numbers for ${number} notice(s)."
+msgstr ""
+"Alle Meldungen von \"${item}\" veröffentlichen? Es werden "
+"Publikationsnummern für ${number} Meldung(en) vergeben."
+
+msgid "Generate PDF"
+msgstr "PDF erstellen"
+
msgid "Issue added."
msgstr "Ausgabe hinzugefügt."
@@ -484,6 +511,15 @@ msgstr "Es können nur unbenutzte Ausgaben gelöscht werden."
msgid "Issue deleted."
msgstr "Ausgabe gelöscht."
+msgid "There are submitted notices for this issue!"
+msgstr "Diese Ausgabe hat eingereichte Meldungen!"
+
+msgid "All notices published."
+msgstr "Alle Meldungen veröffentlicht."
+
+msgid "PDF generated."
+msgstr "PDF erstellt."
+
msgid "Edit Official Notice"
msgstr "Meldung bearbeiten"
@@ -499,9 +535,6 @@ msgstr "Kopieren"
msgid "Preview"
msgstr "Vorschau"
-msgid "Publish"
-msgstr "Veröffentlichen"
-
msgid "Reject"
msgstr "Zurückweisen"
@@ -578,6 +611,17 @@ msgstr "\"${item}\" zurückweisen?"
msgid "Reject Official Note"
msgstr "Meldung zurückweisen"
+#, python-format
+msgid ""
+"Do you really want to publish \"${item}\"? This will assign the publication "
+"numbers for the following issues: ${issues}."
+msgstr ""
+"\"${item}\" veröffentlichen? Es werden Publikationsnummern für die folgenden "
+"Ausgaben vergeben: ${issues}."
+
+msgid "Publish Official Note"
+msgstr "Meldung veröffentlichen"
+
msgid "Only drafted or rejected official notices may be submitted."
msgstr ""
"Es können nur zurückgewiesene Meldungen oder Meldungen in Arbeit eingereicht "
@@ -598,6 +642,12 @@ msgstr "Es können nur eingereichte Meldungen zurückgewiesen werden."
msgid "Official notice rejected."
msgstr "Meldung zurückgewiesen."
+msgid "Only accepted official notices may be published."
+msgstr "Es können nur angenommene Meldungen veröffentlicht werden."
+
+msgid "Official notice published."
+msgstr "Meldung veröffentlicht."
+
msgid "mail sent"
msgstr "Druck beauftragt"
@@ -680,6 +730,3 @@ msgstr "Benutzer gelöscht."
msgid "User account created"
msgstr "Benutzerkonto Amtsblattredaktion erstellt"
-
-#~ msgid "Do you really want to delete the attachment?"
-#~ msgstr "Anhang löschen?"
diff --git a/onegov/gazette/models/issue.py b/onegov/gazette/models/issue.py
index b13d3cd..67e2f41 100644
--- a/onegov/gazette/models/issue.py
+++ b/onegov/gazette/models/issue.py
@@ -1,11 +1,16 @@
from collections import namedtuple
+from onegov.core.crypto import random_token
from onegov.core.orm import Base
from onegov.core.orm.mixins import TimestampMixin
from onegov.core.orm.types import UTCDateTime
+from onegov.file import AssociatedFiles
+from onegov.file import File
+from onegov.file.utils import as_fileintent
from sedate import as_datetime
from sedate import standardize_date
from sqlalchemy import Column
from sqlalchemy import Date
+from sqlalchemy import extract
from sqlalchemy import Integer
from sqlalchemy import Text
from sqlalchemy_utils import observes
@@ -28,7 +33,11 @@ def from_string(cls, value):
return cls(*[int(part) for part in value.split('-')])
-class Issue(Base, TimestampMixin):
+class IssuePdfFile(File):
+ __mapper_args__ = {'polymorphic_identity': 'gazette_issue'}
+
+
+class Issue(Base, TimestampMixin, AssociatedFiles):
""" Defines an issue. """
__tablename__ = 'gazette_issues'
@@ -48,7 +57,22 @@ class Issue(Base, TimestampMixin):
# The deadline of this issue.
deadline = Column(UTCDateTime, nullable=True)
- def notices(self):
+ @property
+ def pdf(self):
+ return self.files[0] if self.files else None
+
+ @pdf.setter
+ def pdf(self, value):
+ filename = '{}.pdf'.format(self.name)
+
+ pdf = self.pdf or IssuePdfFile(id=random_token())
+ pdf.name = filename
+ pdf.reference = as_fileintent(value, filename)
+
+ if not self.pdf:
+ self.files.append(pdf)
+
+ def notices(self, state=None):
""" Returns a query to get all notices related to this issue. """
from onegov.gazette.models.notice import GazetteNotice # circular
@@ -57,9 +81,36 @@ def notices(self):
notices = notices.filter(
GazetteNotice._issues.has_key(self.name) # noqa
)
+ if state:
+ notices = notices.filter(GazetteNotice.state == state)
return notices
+ @staticmethod
+ def publication_numbers(session):
+ """ Returns the current publication numbers by year. """
+
+ from onegov.gazette.models.notice import GazetteNotice # circular
+ from onegov.gazette.collections.issues import IssueCollection # circ.
+
+ result = {}
+
+ for year in IssueCollection(session).years:
+ numbers = []
+
+ issues = session.query(Issue.name)
+ issues = issues.filter(extract('year', Issue.date) == year)
+ for issue in issues:
+ numbers.extend([
+ x[0] for x in session.query(GazetteNotice._issues[issue])
+ if x[0]
+ ])
+ numbers = [int(number) for number in numbers]
+
+ result[year] = max(numbers) if numbers else 0
+
+ return result
+
@property
def in_use(self):
""" True, if the issued is used by any notice. """
@@ -91,3 +142,13 @@ def date_observer(self, date_):
dates = [issues.get(issue, None) for issue in notice._issues]
dates = [date for date in dates if date]
notice.first_issue = min(dates)
+
+ def publish(self, request):
+ """ Ensures that every accepted notice of this issue has been
+ published.
+
+ """
+
+ publication_numbers = None
+ for notice in self.notices('accepted'):
+ publication_numbers = notice.publish(request, publication_numbers)
diff --git a/onegov/gazette/models/notice.py b/onegov/gazette/models/notice.py
index 6cd9209..9702d54 100644
--- a/onegov/gazette/models/notice.py
+++ b/onegov/gazette/models/notice.py
@@ -198,6 +198,31 @@ def accept(self, request):
super(GazetteNotice, self).accept()
self.add_change(request, _("accepted"))
+ def publish(self, request, publication_numbers=None):
+ """ Publish an accepted notice.
+
+ This automatically adds en entry to the changelog and assigns the
+ publication numbers.
+
+ Returns the updated publications numbers to allow batch-publishing.
+
+ """
+
+ if not publication_numbers:
+ session = request.app.session()
+ publication_numbers = Issue.publication_numbers(session)
+
+ issues = dict(self.issues)
+ for issue in self.issue_objects:
+ publication_numbers[issue.date.year] += 1
+ issues[issue.name] = str(publication_numbers[issue.date.year])
+ self._issues = issues
+
+ super(GazetteNotice, self).publish()
+ self.add_change(request, _("published"))
+
+ return publication_numbers
+
@property
def rejected_comment(self):
""" Returns the comment of the last rejected change log entry. """
diff --git a/onegov/gazette/models/principal.py b/onegov/gazette/models/principal.py
index e4b40f0..1f97a4b 100644
--- a/onegov/gazette/models/principal.py
+++ b/onegov/gazette/models/principal.py
@@ -12,7 +12,8 @@ def __init__(
publish_to='',
publish_from='',
time_zone='Europe/Zurich',
- help_link=''
+ help_link='',
+ show_archive=False
):
self.name = name
self.logo = logo
@@ -21,6 +22,7 @@ def __init__(
self.publish_from = publish_from
self.time_zone = time_zone
self.help_link = help_link
+ self.show_archive = show_archive
@classmethod
def from_yaml(cls, yaml_source):
diff --git a/onegov/gazette/path.py b/onegov/gazette/path.py
index bebd6c8..51fb67c 100644
--- a/onegov/gazette/path.py
+++ b/onegov/gazette/path.py
@@ -1,5 +1,6 @@
from onegov.core.converters import extended_date_converter
from onegov.core.converters import uuid_converter
+from onegov.file.integration import get_file
from onegov.gazette import GazetteApp
from onegov.gazette.collections import CategoryCollection
from onegov.gazette.collections import GazetteNoticeCollection
@@ -11,6 +12,7 @@
from onegov.gazette.models import Organization
from onegov.gazette.models import OrganizationMove
from onegov.gazette.models import Principal
+from onegov.gazette.models.issue import IssuePdfFile
from onegov.user import Auth
from onegov.user import User
from onegov.user import UserCollection
@@ -68,6 +70,14 @@ def get_organization(app, id):
return OrganizationCollection(app.session()).by_id(id)
+@GazetteApp.path(
+ model=OrganizationMove,
+ path='/move/organization/{subject_id}/{direction}/{target_id}',
+ converters=dict(subject_id=int, target_id=int))
+def get_page_move(app, subject_id, direction, target_id):
+ return OrganizationMove(app.session(), subject_id, target_id, direction)
+
+
@GazetteApp.path(model=IssueCollection, path='/issues')
def get_issues(app):
return IssueCollection(app.session())
@@ -78,12 +88,11 @@ def get_issue(app, id):
return IssueCollection(app.session()).by_id(id)
-@GazetteApp.path(
- model=OrganizationMove,
- path='/move/organization/{subject_id}/{direction}/{target_id}',
- converters=dict(subject_id=int, target_id=int))
-def get_page_move(app, subject_id, direction, target_id):
- return OrganizationMove(app.session(), subject_id, target_id, direction)
+@GazetteApp.path(model=IssuePdfFile, path='/pdf/{name}')
+def get_issue_pdf(request, app, name):
+ issue = IssueCollection(app.session()).by_name(name.replace('.pdf', ''))
+ if issue and issue.pdf:
+ return get_file(app, issue.pdf.id)
@GazetteApp.path(
diff --git a/onegov/gazette/pdf.py b/onegov/gazette/pdf.py
new file mode 100644
index 0000000..54662ad
--- /dev/null
+++ b/onegov/gazette/pdf.py
@@ -0,0 +1,186 @@
+from copy import deepcopy
+from io import BytesIO
+from onegov.gazette import _
+from onegov.gazette.layout import Layout
+from onegov.gazette.models import Category
+from onegov.gazette.models import GazetteNotice
+from onegov.gazette.models import Organization
+from onegov.pdf import page_fn_footer
+from onegov.pdf import page_fn_header_and_footer
+from onegov.pdf import Pdf as PdfBase
+
+
+class Pdf(PdfBase):
+
+ def adjust_style(self, font_size=10):
+ """ Adds styles for notices. """
+
+ super(Pdf, self).adjust_style(font_size)
+
+ self.style.title = deepcopy(self.style.normal)
+ self.style.title.fontSize = 2.25 * self.style.fontSize
+ self.style.title.leading = 1.2 * self.style.title.fontSize
+ self.style.title.spaceBefore = 0
+ self.style.title.spaceAfter = 0.67 * self.style.title.fontSize
+
+ self.style.h_notice = deepcopy(self.style.normal)
+ self.style.h_notice.fontSize = 1.125 * self.style.fontSize
+ self.style.h_notice.spaceBefore = 1.275 * self.style.h_notice.fontSize
+ self.style.h_notice.spaceAfter = 0.275 * self.style.h_notice.fontSize
+
+ self.style.paragraph.spaceAfter = 0.675 * self.style.paragraph.fontSize
+ self.style.paragraph.leading = 1.275 * self.style.paragraph.fontSize
+
+ self.style.ul_bullet = '-'
+ self.style.li.spaceAfter = 0.275 * self.style.li.fontSize
+ self.style.li.leading = 1.275 * self.style.li.fontSize
+
+ def h(self, title, level=0):
+ """ Adds a title according to the given level. """
+
+ if not level:
+ self.p_markup(title, self.style.title)
+ else:
+ getattr(self, 'h{}'.format(min(level, 4)))(title)
+
+ def unfold_data(self, data, level=1):
+ """ Take a nested list of dicts and add it. """
+
+ for item in data:
+ title = item.get('title', None)
+ if title:
+ self.h(title, level)
+ self.story[-1].keepWithNext = True
+
+ notices = item.get('notices', [])
+ for notice in notices:
+ self.p_markup(
+ '{} {}'.format(
+ notice[0],
+ 0.875 * self.style.h_notice.fontSize,
+ notice[2]
+ ),
+ self.style.h_notice
+ )
+ self.story[-1].keepWithNext = True
+ self.mini_html(notice[1])
+
+ children = item.get('children', [])
+ if children:
+ self.unfold_data(children, level + 1)
+
+ @staticmethod
+ def query_notices(session, issue, organization, category):
+ """ Queries all notices with the given values, ordered by publication
+ number.
+
+ """
+
+ notices = session.query(
+ GazetteNotice.title,
+ GazetteNotice.text,
+ GazetteNotice._issues[issue]
+ )
+ notices = notices.filter(
+ GazetteNotice._issues.has_key(issue), # noqa
+ GazetteNotice.state == 'published',
+ GazetteNotice._organizations.has_key(organization),
+ GazetteNotice._categories.has_key(category)
+ )
+ notices = notices.order_by(
+ GazetteNotice._issues[issue]
+ )
+ return notices.all()
+
+ @classmethod
+ def from_issue(cls, issue, request, file=None):
+ """ Generate a PDF for one issue. """
+
+ # Collect the data
+ data = []
+
+ session = request.app.session()
+
+ used_categories = session.query(GazetteNotice._categories.keys())
+ used_categories = used_categories.filter(
+ GazetteNotice._issues.has_key(issue.name), # noqa
+ GazetteNotice.state == 'published',
+ )
+ used_categories = [cat[0][0] for cat in used_categories]
+
+ used_organizations = session.query(GazetteNotice._organizations.keys())
+ used_organizations = used_organizations.filter(
+ GazetteNotice._issues.has_key(issue.name), # noqa
+ GazetteNotice.state == 'published',
+ )
+ used_organizations = [cat[0][0] for cat in used_organizations]
+
+ if used_categories and used_organizations:
+ categories = session.query(Category)
+ categories = categories.filter(Category.name.in_(used_categories))
+ categories = categories.order_by(Category.order).all()
+
+ roots = session.query(Organization).filter_by(parent_id=None)
+ roots = roots.order_by(Organization.order)
+
+ for root in roots:
+ root_data = []
+ if not root.children:
+ for category in categories:
+ notices = cls.query_notices(
+ session, issue.name, root.name, category.name
+ )
+ if notices:
+ root_data.append({
+ 'title': category.title,
+ 'notices': notices
+ })
+ else:
+ for child in root.children:
+ if child.name not in used_organizations:
+ continue
+ child_data = []
+ for category in categories:
+ notices = cls.query_notices(
+ session, issue.name, child.name, category.name
+ )
+ if notices:
+ child_data.append({
+ 'title': category.title,
+ 'notices': notices
+ })
+ if child_data:
+ root_data.append({
+ 'title': child.title,
+ 'children': child_data
+ })
+ if root_data:
+ data.append({
+ 'title': root.title,
+ 'children': root_data
+ })
+
+ # Generate the PDF
+ layout = Layout(None, request)
+ title = '{} {}'.format(
+ request.translate(_("Gazette")),
+ layout.format_issue(issue, date_format='date')
+ )
+
+ file = file or BytesIO()
+ pdf = cls(
+ file,
+ title=title,
+ author=request.app.principal.name
+ )
+ pdf.init_a4_portrait(
+ page_fn=page_fn_footer,
+ page_fn_later=page_fn_header_and_footer
+ )
+ pdf.h(title)
+ pdf.unfold_data(data)
+ pdf.generate()
+
+ file.seek(0)
+
+ return file
diff --git a/onegov/gazette/templates/archive.pt b/onegov/gazette/templates/archive.pt
new file mode 100644
index 0000000..36b6f73
--- /dev/null
+++ b/onegov/gazette/templates/archive.pt
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ ${year}
+
+
+
+
+
+
+
diff --git a/onegov/gazette/templates/issues.pt b/onegov/gazette/templates/issues.pt
index 3787ef6..7beef12 100644
--- a/onegov/gazette/templates/issues.pt
+++ b/onegov/gazette/templates/issues.pt
@@ -27,6 +27,7 @@
Issue |
Date |
Deadline |
+ PDF |
Actions |
@@ -39,11 +40,20 @@
${issue.name} |
${layout.format_date(issue.date, 'date')} |
${layout.format_date(issue.deadline, 'datetime_with_weekday')} |
+
+ ${issue.pdf.name}
+ |
-
Edit
+ -
+ Publish
+
+ -
+ Generate
+
-
Delete
@@ -61,6 +71,7 @@
Issue |
Date |
Deadline |
+ PDF |
Actions |
@@ -73,11 +84,20 @@
${issue.name} |
${layout.format_date(issue.date, 'date')} |
${layout.format_date(issue.deadline, 'datetime_with_weekday')} |
+
+ ${issue.pdf.name}
+ |
-
Edit
+ -
+ Publish
+
+ -
+ Generate
+
-
Delete
diff --git a/onegov/gazette/templates/macros.pt b/onegov/gazette/templates/macros.pt
index 1d8692d..30a54c1 100644
--- a/onegov/gazette/templates/macros.pt
+++ b/onegov/gazette/templates/macros.pt
@@ -122,10 +122,10 @@
- - ${layout.format_issue(issue)}
+ - ${layout.format_issue(issue, notice=notice)}
- ${layout.format_issue(issue)}
+ ${layout.format_issue(issue, notice=notice)}
diff --git a/onegov/gazette/tests/__init__.py b/onegov/gazette/tests/__init__.py
index 9fb7453..ff7d5de 100644
--- a/onegov/gazette/tests/__init__.py
+++ b/onegov/gazette/tests/__init__.py
@@ -98,6 +98,18 @@ def reject_notice(user, slug, unable=False, forbidden=False):
assert "Meldung zurückgewiesen" in manage.maybe_follow()
+def publish_notice(user, slug, unable=False, forbidden=False):
+ url = '/notice/{}/publish'.format(slug)
+ if unable:
+ assert not user.get(url).forms
+ elif forbidden:
+ assert user.get(url, status=403)
+ else:
+ manage = user.get(url)
+ manage = manage.form.submit()
+ assert "Meldung veröffentlicht" in manage.maybe_follow()
+
+
def edit_notice(user, slug, unable=False, forbidden=False, **kwargs):
url = '/notice/{}/edit'.format(slug)
if unable:
diff --git a/onegov/gazette/tests/test_collections.py b/onegov/gazette/tests/test_collections.py
index 33137c3..ce52c55 100644
--- a/onegov/gazette/tests/test_collections.py
+++ b/onegov/gazette/tests/test_collections.py
@@ -8,6 +8,7 @@
from onegov.user import UserCollection
from onegov.user import UserGroupCollection
from sedate import standardize_date
+from collections import OrderedDict
class DummyApp(object):
@@ -79,20 +80,46 @@ def test_issue_collection(session):
name='2017-2', number=2, date=date(2017, 2, 2),
deadline=standardize_date(datetime(2017, 2, 1, 12, 0), 'UTC')
)
+ issue_4 = collection.add(
+ name='2018-1', number=1, date=date(2018, 1, 1),
+ deadline=standardize_date(datetime(2017, 12, 20, 12, 0), 'UTC')
+ )
+ # test query
assert [issue.name for issue in collection.query()] == [
- '2017-1', '2017-2', '2017-3'
+ '2017-1', '2017-2', '2017-3', '2018-1'
]
+ # test current issue
with freeze_time("2017-01-01 11:00"):
assert collection.current_issue == issue_1
with freeze_time("2017-01-01 13:00"):
assert collection.current_issue == issue_2
with freeze_time("2017-02-10 13:00"):
assert collection.current_issue == issue_3
- with freeze_time("2017-04-10 13:00"):
+ with freeze_time("2017-12-10 13:00"):
+ assert collection.current_issue == issue_4
+ with freeze_time("2018-04-10 13:00"):
assert collection.current_issue is None
+ # test by name
+ assert collection.by_name('2017-1') == issue_1
+ assert collection.by_name('2017-2') == issue_2
+ assert collection.by_name('2017-3') == issue_3
+ assert collection.by_name('2018-1') == issue_4
+ assert collection.by_name('2018-2') is None
+
+ # test years
+ assert collection.years == [2017, 2018]
+ assert collection.by_years() == OrderedDict((
+ (2017, [issue_1, issue_2, issue_3]),
+ (2018, [issue_4]),
+ ))
+ assert collection.by_years(desc=True) == OrderedDict((
+ (2018, [issue_4]),
+ (2017, [issue_3, issue_2, issue_1]),
+ ))
+
def test_notice_collection(session, organizations, categories, issues):
user = UserCollection(session).add(
diff --git a/onegov/gazette/tests/test_fields.py b/onegov/gazette/tests/test_fields.py
index 530518a..abac61c 100644
--- a/onegov/gazette/tests/test_fields.py
+++ b/onegov/gazette/tests/test_fields.py
@@ -31,6 +31,16 @@ def test_multi_checkbox_field():
assert 'data-expand-title="Show all"' in field()
assert 'data-limit="10"' in field()
+ form = Form()
+ field = MultiCheckboxField(
+ choices=(('a', 'b'),),
+ render_kw={'disabled': True}
+ )
+ field = field.bind(form, 'choice')
+
+ field.data = ''
+ assert 'input disabled' in field()
+
def test_date_time_local_field():
form = Form()
diff --git a/onegov/gazette/tests/test_forms.py b/onegov/gazette/tests/test_forms.py
index 0253006..880b048 100644
--- a/onegov/gazette/tests/test_forms.py
+++ b/onegov/gazette/tests/test_forms.py
@@ -553,6 +553,41 @@ def test_notice_form(session, categories, organizations, issues):
def test_unrestricted_notice_form(session, categories, organizations, issues):
+ # Test apply / update
+ form = UnrestrictedNoticeForm()
+ form.request = DummyRequest(session)
+
+ notice = GazetteNotice(title='Title', text='A text.')
+ notice.organization_id = '200'
+ notice.category_id = '13'
+ notice.issues = ['2017-43']
+
+ form.apply_model(notice)
+ assert form.title.data == 'Title'
+ assert form.organization.data == '200'
+ assert form.category.data == '13'
+ assert form.text.data == 'A text.'
+ assert form.issues.data == ['2017-43']
+
+ form.title.data = 'Notice'
+ form.organization.data = '300'
+ form.category.data = '11'
+ form.text.data = 'A notice.'
+ form.issues.data = ['2017-44']
+
+ form.update_model(notice)
+ assert notice.title == 'Notice'
+ assert notice.organization == 'Municipality'
+ assert notice.category == 'Education'
+ assert notice.text == 'A notice.'
+ assert notice.issues == {'2017-44': None}
+ assert notice.first_issue == standardize_date(datetime(2017, 11, 3), 'UTC')
+
+ notice.state = 'published'
+ form.issues.data = ['2017-45']
+ form.update_model(notice)
+ assert notice.issues == {'2017-44': None}
+
# Test on request
with freeze_time("2019-11-01 14:00"):
form = UnrestrictedNoticeForm()
@@ -592,3 +627,12 @@ def test_unrestricted_notice_form(session, categories, organizations, issues):
('14', 'Elections'),
('12', 'Submissions'),
]
+
+ # Test disable issues
+ form = UnrestrictedNoticeForm()
+ form.model = None
+ form.request = DummyRequest(session)
+ form.on_request()
+ form.disable_issues()
+ assert form.issues.validators == []
+ assert all([field.render_kw['disabled'] for field in form.issues])
diff --git a/onegov/gazette/tests/test_layout.py b/onegov/gazette/tests/test_layout.py
index f59f828..d0d8089 100644
--- a/onegov/gazette/tests/test_layout.py
+++ b/onegov/gazette/tests/test_layout.py
@@ -3,6 +3,7 @@
from freezegun import freeze_time
from onegov.gazette.layout import Layout
from onegov.gazette.models import Issue
+from onegov.gazette.models import GazetteNotice
from pytest import raises
from sedate import standardize_date
@@ -75,7 +76,7 @@ def test_layout_menu():
request._is_personal = True
assert layout.menu == [
('My Drafted and Submitted Official Notices', '/dashboard/', False),
- ('My Accepted Official Notices', '/GazetteNoticeCollection/', False)
+ ('My Published Official Notices', '/GazetteNoticeCollection/', False)
]
request._is_private = True
@@ -129,11 +130,26 @@ def test_layout_format(session, principal):
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))
+ ) == 'No. 1, 02.01.2017'
assert layout.format_issue(
- Issue(number=1, date=date(2017, 1, 2)), date_format='date_with_weekday'
+ 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) == ''
diff --git a/onegov/gazette/tests/test_models.py b/onegov/gazette/tests/test_models.py
index d80b1a4..160c78b 100644
--- a/onegov/gazette/tests/test_models.py
+++ b/onegov/gazette/tests/test_models.py
@@ -11,6 +11,7 @@
from onegov.gazette.models import Organization
from onegov.gazette.models import OrganizationMove
from onegov.gazette.models import Principal
+from onegov.gazette.models.issue import IssuePdfFile
from onegov.gazette.models.notice import GazetteNoticeChange
from onegov.user import UserCollection
from onegov.user import UserGroupCollection
@@ -19,6 +20,20 @@
from textwrap import dedent
+class DummyApp(object):
+ def __init__(self, session):
+ self._session = session
+
+ def session(self):
+ return self._session
+
+
+class DummyRequest(object):
+ def __init__(self, session):
+ self.app = DummyApp(session)
+ self.identity = None
+
+
def test_category(session):
session.add(
Category(
@@ -149,17 +164,35 @@ def test_issue_name():
assert IssueName.from_string(str(issue_name)) == issue_name
-def test_issue(session):
+def test_issue_file(gazette_app, session):
session.add(
- Issue(
- id=0,
- name='2018-7',
- number=7,
- date=date(2017, 7, 1),
- deadline=standardize_date(datetime(2017, 6, 25, 12, 0), 'UTC')
+ IssuePdfFile(
+ id='abcd',
+ name='test.txt',
+ reference=as_fileintent('Test text.'.encode('utf-8'), 'test.txt')
)
)
session.flush()
+
+ file = session.query(IssuePdfFile).one()
+
+ assert file.id == 'abcd'
+ assert file.name == 'test.txt'
+ assert file.type == 'gazette_issue'
+ assert file.reference.file.read().decode('utf-8') == 'Test text.'
+
+
+def test_issue(gazette_app, session):
+ issue = Issue(
+ id=0,
+ name='2018-7',
+ number=7,
+ date=date(2017, 7, 1),
+ deadline=standardize_date(datetime(2017, 6, 25, 12, 0), 'UTC'),
+ )
+ issue.pdf = 'PDF'.encode('utf-8')
+ session.add(issue)
+ session.flush()
issue = session.query(Issue).one()
assert issue.id == 0
@@ -169,9 +202,15 @@ def test_issue(session):
assert issue.deadline == standardize_date(
datetime(2017, 6, 25, 12, 0), 'UTC'
)
+ assert issue.pdf.id
+ assert issue.pdf.name == '2018-7.pdf'
+ assert issue.pdf.type == 'gazette_issue'
+ assert issue.pdf.reference.file.read().decode('utf-8') == 'PDF'
# Test query etc
assert len(issue.notices().all()) == 0
+ assert issue.notices('accepted').all() == []
+ assert issue.notices('submitted').all() == []
assert issue.in_use is False
issues = [issue.name]
@@ -183,6 +222,8 @@ def test_issue(session):
session.flush()
assert len(issue.notices().all()) == 4
+ assert issue.notices('accepted').all()[0].title == 'a'
+ assert issue.notices('submitted').one().title == 's'
assert issue.in_use is True
# Test date observer
@@ -192,6 +233,39 @@ def test_issue(session):
dates = [d.date() for d in dates if d]
assert set(dates) == set([issue.date])
+ # Test publish
+ issue.publish(DummyRequest(session))
+ assert len(issue.notices().all()) == 4
+ assert issue.notices('accepted').all() == []
+ assert issue.notices('published').one().issues == {'2018-7': '1'}
+
+
+def test_issue_publication_numbers(session):
+ assert Issue.publication_numbers(session) == {}
+
+ session.add(Issue(name='2016-1', number=1, date=date(2016, 1, 1)))
+ session.add(Issue(name='2017-1', number=1, date=date(2017, 1, 1)))
+ session.add(Issue(name='2017-2', number=2, date=date(2017, 2, 2)))
+ session.add(Issue(name='2018-1', number=1, date=date(2018, 1, 1)))
+ session.add(Issue(name='2018-2', number=2, date=date(2018, 2, 2)))
+ session.add(Issue(name='2018-3', number=2, date=date(2018, 3, 3)))
+ session.flush()
+
+ assert Issue.publication_numbers(session) == {2016: 0, 2017: 0, 2018: 0}
+
+ session.add(GazetteNotice(title='1', _issues={'2016-1': '5'}))
+ session.add(GazetteNotice(title='2', _issues={'2017-1': None}))
+ session.add(GazetteNotice(title='3', _issues={'2017-1': '5',
+ '2017-2': '6'}))
+ session.add(GazetteNotice(title='4', _issues={'2017-1': None,
+ '2018-3': '4'}))
+ session.add(GazetteNotice(title='4', _issues={'2017-2': '9',
+ '2018-1': '1',
+ '2018-2': '2'}))
+ session.flush()
+
+ assert Issue.publication_numbers(session) == {2016: 5, 2017: 9, 2018: 4}
+
def test_principal():
principal = Principal.from_yaml(dedent("""
@@ -205,6 +279,7 @@ def test_principal():
assert principal.logo == 'logo.svg'
assert principal.publish_to == 'printer@govikon.org'
assert principal.publish_from == ''
+ assert principal.show_archive is False
principal = Principal.from_yaml(dedent("""
name: Govikon
@@ -213,6 +288,7 @@ def test_principal():
publish_to: 'printer@govikon.org'
publish_from: 'publisher@govikon.org'
help_link: 'https://help.me'
+ show_archive: True
"""))
assert principal.name == 'Govikon'
assert principal.color == '#aabbcc'
@@ -220,6 +296,7 @@ def test_principal():
assert principal.publish_to == 'printer@govikon.org'
assert principal.publish_from == 'publisher@govikon.org'
assert principal.help_link == 'https://help.me'
+ assert principal.show_archive is True
def test_notice_organization(session):
@@ -425,24 +502,25 @@ def test_notice_states(session):
class DummyIdentity():
userid = None
- class DummyRequest():
- identity = None
-
user = UserCollection(session).add('1@2.com', 'test', 'publisher')
session.add(GazetteNotice(state='drafted', title='title', name='notice'))
session.flush()
notice = session.query(GazetteNotice).one()
- request = DummyRequest()
+ request = DummyRequest(session)
with raises(AssertionError):
notice.accept(request)
with raises(AssertionError):
notice.reject(request, 'XXX')
+ with raises(AssertionError):
+ notice.publish(request)
notice.submit(request)
with raises(AssertionError):
notice.submit(request)
+ with raises(AssertionError):
+ notice.publish(request)
notice.reject(request, 'Some reason')
notice.submit(request)
notice.reject(request, 'Some other reason')
@@ -456,6 +534,17 @@ class DummyRequest():
with raises(AssertionError):
notice.reject(request, 'Some reason')
+ notice.publish(request)
+
+ with raises(AssertionError):
+ notice.submit(request)
+ with raises(AssertionError):
+ notice.accept(request)
+ with raises(AssertionError):
+ notice.reject(request, 'Some reason')
+ with raises(AssertionError):
+ notice.publish(request)
+
notice.add_change(request, 'printed')
request.identity = DummyIdentity()
@@ -468,7 +557,6 @@ class DummyRequest():
# ourselves
changes = notice.changes.order_by(None)
changes = changes.order_by(GazetteNoticeChange.edited.desc())
- [(change.event, change.user, change.text) for change in changes]
assert [
(change.event, change.user, change.text) for change in changes
] == [
@@ -478,6 +566,7 @@ class DummyRequest():
('rejected', None, 'Some other reason'),
('submitted', None, ''),
('accepted', None, ''),
+ ('published', None, ''),
('printed', None, ''),
('finished', user, 'all went well')
]
@@ -490,6 +579,46 @@ class DummyRequest():
]
+def test_notice_publish(session):
+ request = DummyRequest(session)
+
+ # No issues
+ session.add(
+ GazetteNotice(state='accepted', title='title', name='notice')
+ )
+ session.flush()
+ notice = session.query(GazetteNotice).filter_by(state='accepted').one()
+ notice.publish(request)
+ assert notice.issues == {}
+
+ # With issues
+ session.add(Issue(name='2016-1', number=1, date=date(2016, 1, 1)))
+ session.add(Issue(name='2017-1', number=1, date=date(2017, 1, 1)))
+ session.add(Issue(name='2017-2', number=1, date=date(2017, 2, 2)))
+ session.add(Issue(name='2018-1', number=1, date=date(2018, 1, 1)))
+ session.add(
+ GazetteNotice(
+ state='accepted', title='title', name='notice',
+ issues=['2017-1', '2017-2', '2018-1']
+ )
+ )
+ session.flush()
+ notice = session.query(GazetteNotice).filter_by(state='accepted').one()
+ notice.publish(request)
+ assert notice.issues == {'2017-1': '1', '2017-2': '2', '2018-1': '1'}
+
+ session.add(
+ GazetteNotice(
+ state='accepted', title='title', name='notice',
+ issues=['2017-1', '2017-3']
+ )
+ )
+ session.flush()
+ notice = session.query(GazetteNotice).filter_by(state='accepted').one()
+ notice.publish(request)
+ assert notice.issues == {'2017-1': '3', '2017-3': None}
+
+
def test_notice_apply_meta(session, categories, organizations, issues):
notice = GazetteNotice()
diff --git a/onegov/gazette/tests/test_pdf.py b/onegov/gazette/tests/test_pdf.py
new file mode 100644
index 0000000..e826c9f
--- /dev/null
+++ b/onegov/gazette/tests/test_pdf.py
@@ -0,0 +1,278 @@
+from freezegun import freeze_time
+from io import BytesIO
+from onegov.gazette.models import GazetteNotice
+from onegov.gazette.models import Issue
+from onegov.gazette.pdf import Pdf
+from PyPDF2 import PdfFileReader
+from unittest.mock import patch
+
+
+class DummyApp(object):
+ def __init__(self, session, principal):
+ self._session = session
+ self.principal = principal
+
+ def session(self):
+ return self._session
+
+
+class DummyRequest(object):
+ def __init__(self, session, principal):
+ self.app = DummyApp(session, principal)
+ self.locale = 'de_CH'
+
+ def translate(self, text):
+ return text.interpolate()
+
+ def include(self, resource):
+ pass
+
+
+def test_pdf_h():
+ pdf = Pdf(BytesIO())
+ pdf.init_a4_portrait()
+
+ with patch.object(pdf, 'h1') as h1:
+ pdf.h('h1', 1)
+ assert h1.called
+
+ with patch.object(pdf, 'h2') as h2:
+ pdf.h('h2', 2)
+ assert h2.called
+
+ with patch.object(pdf, 'h3') as h3:
+ pdf.h('h3', 3)
+ assert h3.called
+
+ with patch.object(pdf, 'h4') as h4:
+ pdf.h('h4', 4)
+ assert h4.called
+
+ with patch.object(pdf, 'h4') as h4:
+ pdf.h('h5', 5)
+ assert h4.called
+
+
+def test_pdf_unfold_data():
+ data = [
+ {
+ 'title': 'title-1',
+ 'children': [
+ {
+ 'title': 'title-1-1',
+ 'children': [{'title': 'title-1-1-1'}],
+ 'notices': [['title-1-1-a', 'text-1-1-a', '1']]
+ },
+ {
+ 'title': 'title-1-2',
+ 'notices': [
+ ['title-1-2-a', 'text-1-2-a', '1'],
+ ['title-1-2-b', 'text-1-2-b', '1'],
+ ['title-1-2-c', 'text-1-2-c', '1'],
+ ],
+ 'children': [{
+ 'notices': [
+ ['title-1-2-1-a', 'text-1-2-1-a', '1'],
+ ['title-1-2-1-b', 'text-1-2-1-b', '1'],
+ ]
+ }]
+ }
+ ]
+ },
+ {
+ 'title': 'title-2',
+
+ 'notices': [['title-2-a', 'text-2-a', '1']],
+ 'children': [
+ {'notices': [['title-2-1-a', 'text-2-1-a', '1']]},
+ {'notices': [['title-2-2-a', 'text-2-2-a', '1']]},
+ {'notices': [['title-2-3-a', 'text-2-3-a', '1']]}
+ ]
+ },
+ {
+ 'notices': [
+ ['title-3-a', 'text-3-a', '1'],
+ ['title-3-b', 'text-3-b', '1'],
+ ['title-3-c', 'text-3-c', '1'],
+ ['title-3-d', 'text-3-d', '1'],
+ ]
+ }
+ ]
+
+ file = BytesIO()
+ pdf = Pdf(file)
+ pdf.init_a4_portrait()
+ pdf.unfold_data(data)
+
+ expected = [
+ 'title-1',
+ 'title-1-1',
+ 'title-1-1-a 1', 'text-1-1-a',
+ 'title-1-1-1',
+ 'title-1-2',
+ 'title-1-2-a 1', 'text-1-2-a',
+ 'title-1-2-b 1', 'text-1-2-b',
+ 'title-1-2-c 1', 'text-1-2-c',
+ 'title-1-2-1-a 1', 'text-1-2-1-a',
+ 'title-1-2-1-b 1', 'text-1-2-1-b',
+ 'title-2',
+ 'title-2-a 1', 'text-2-a',
+ 'title-2-1-a 1', 'text-2-1-a',
+ 'title-2-2-a 1', 'text-2-2-a',
+ 'title-2-3-a 1', 'text-2-3-a',
+ 'title-3-a 1', 'text-3-a',
+ 'title-3-b 1', 'text-3-b',
+ 'title-3-c 1', 'text-3-c',
+ 'title-3-d 1', 'text-3-d',
+ ]
+
+ story = [
+ x.text.replace('', '')
+ .replace('', '')
+ .replace('', '')
+ .replace('', '')
+ for x in pdf.story if hasattr(x, 'text')
+ ]
+ assert story == expected
+
+ pdf.generate()
+ file.seek(0)
+ reader = PdfFileReader(file)
+ text = ''.join([page.extractText() for page in reader.pages])
+ assert text.strip() == '\n'.join(expected)
+
+
+def test_pdf_query_notices(session, issues, organizations, categories):
+ for _issues, organization_id, category_id in (
+ ({'2017-40': '1', '2017-41': '4'}, '100', '10'),
+ ({'2017-40': '2', '2017-41': '3'}, '100', '10'),
+ ({'2017-40': '3', '2017-41': '2'}, '100', '10'),
+ ({'2017-40': '4', '2017-41': '1'}, '100', '10'),
+ ({'2017-41': '5', '2017-42': '1'}, '100', '10'),
+ ):
+ session.add(
+ GazetteNotice(
+ title='{}-{}'.format(organization_id, category_id),
+ text=', '.join([
+ '{}-{}'.format(issue, _issues[issue])
+ for issue in sorted(_issues)
+ ]),
+ _issues=_issues,
+ organization_id=organization_id,
+ category_id=category_id,
+ state='published'
+ )
+ )
+ session.flush()
+
+ assert Pdf.query_notices(session, '2017-40', '100', '10') == [
+ ('100-10', '2017-40-1, 2017-41-4', '1'),
+ ('100-10', '2017-40-2, 2017-41-3', '2'),
+ ('100-10', '2017-40-3, 2017-41-2', '3'),
+ ('100-10', '2017-40-4, 2017-41-1', '4')
+ ]
+ assert Pdf.query_notices(session, '2017-41', '100', '10') == [
+ ('100-10', '2017-40-4, 2017-41-1', '1'),
+ ('100-10', '2017-40-3, 2017-41-2', '2'),
+ ('100-10', '2017-40-2, 2017-41-3', '3'),
+ ('100-10', '2017-40-1, 2017-41-4', '4'),
+ ('100-10', '2017-41-5, 2017-42-1', '5')
+ ]
+ assert Pdf.query_notices(session, '2017-42', '100', '10') == [
+ ('100-10', '2017-41-5, 2017-42-1', '1')
+ ]
+
+
+def test_pdf_from_issue(gazette_app):
+ session = gazette_app.session()
+ principal = gazette_app.principal
+
+ for _issues, organization_id, category_id in (
+ ({'2017-40': '1', '2017-41': '4'}, '100', '10'),
+ ({'2017-40': '2', '2017-41': '3'}, '200', '10'),
+ ({'2017-40': '3', '2017-41': '2'}, '100', '10'),
+ ({'2017-40': '4', '2017-41': '1'}, '410', '11'),
+ ({'2017-41': '5', '2017-42': '1'}, '420', '11'),
+ ):
+ session.add(
+ GazetteNotice(
+ title='{}-{}'.format(organization_id, category_id),
+ text=', '.join([
+ '{}-{}'.format(issue, _issues[issue])
+ for issue in sorted(_issues)
+ ]),
+ _issues=_issues,
+ organization_id=organization_id,
+ category_id=category_id,
+ state='published'
+ )
+ )
+ session.flush()
+
+ with freeze_time("2017-01-01 12:00"):
+ issue = session.query(Issue).filter_by(number=40).one()
+ file = Pdf.from_issue(issue, DummyRequest(session, principal))
+ reader = PdfFileReader(file)
+ text = ''.join([page.extractText() for page in reader.pages])
+ assert text == (
+ '© 2017 Govikon\n1\n'
+ 'Gazette No. 40, 06.10.2017\n'
+ 'State Chancellery\n'
+ 'Complaints\n'
+ '100-10 1\n'
+ '2017-40-1, 2017-41-4\n'
+ '100-10 3\n'
+ '2017-40-3, 2017-41-2\n'
+ 'Civic Community\n'
+ 'Complaints\n'
+ '200-10 2\n'
+ '2017-40-2, 2017-41-3\n'
+ 'Churches\n'
+ 'Evangelical Reformed Parish\n'
+ 'Education\n'
+ '410-11 4\n'
+ '2017-40-4, 2017-41-1\n'
+ )
+
+ issue = session.query(Issue).filter_by(number=41).one()
+ file = Pdf.from_issue(issue, DummyRequest(session, principal))
+ reader = PdfFileReader(file)
+ text = ''.join([page.extractText() for page in reader.pages])
+ assert text == (
+ '© 2017 Govikon\n1\n'
+ 'Gazette No. 41, 13.10.2017\n'
+ 'State Chancellery\n'
+ 'Complaints\n'
+ '100-10 2\n'
+ '2017-40-3, 2017-41-2\n'
+ '100-10 4\n'
+ '2017-40-1, 2017-41-4\n'
+ 'Civic Community\n'
+ 'Complaints\n'
+ '200-10 3\n'
+ '2017-40-2, 2017-41-3\n'
+ 'Churches\n'
+ 'Evangelical Reformed Parish\n'
+ 'Education\n'
+ '410-11 1\n'
+ '2017-40-4, 2017-41-1\n'
+ 'Sikh Community\n'
+ 'Education\n'
+ '420-11 5\n'
+ '2017-41-5, 2017-42-1\n'
+ )
+
+ with freeze_time("2018-01-01 12:00"):
+ issue = session.query(Issue).filter_by(number=42).one()
+ file = Pdf.from_issue(issue, DummyRequest(session, principal))
+ reader = PdfFileReader(file)
+ text = ''.join([page.extractText() for page in reader.pages])
+ assert text == (
+ '© 2018 Govikon\n1\n'
+ 'Gazette No. 42, 20.10.2017\n'
+ 'Churches\n'
+ 'Sikh Community\n'
+ 'Education\n'
+ '420-11 1\n'
+ '2017-41-5, 2017-42-1\n'
+ )
diff --git a/onegov/gazette/tests/test_views_auth.py b/onegov/gazette/tests/test_views_auth.py
index d0a0383..edc0a44 100644
--- a/onegov/gazette/tests/test_views_auth.py
+++ b/onegov/gazette/tests/test_views_auth.py
@@ -22,7 +22,7 @@ def test_view_login_logout(gazette_app):
('editor3@example.org', 'Third Editor'),
('publisher@example.org', 'Publisher'),
):
- login = client.get('/').maybe_follow()
+ login = client.get('/').maybe_follow().click('Anmelden')
login.form['username'] = username
login.form['password'] = 'hunter1'
diff --git a/onegov/gazette/tests/test_views_issues.py b/onegov/gazette/tests/test_views_issues.py
index b94e20a..9bd1322 100644
--- a/onegov/gazette/tests/test_views_issues.py
+++ b/onegov/gazette/tests/test_views_issues.py
@@ -1,6 +1,8 @@
from freezegun import freeze_time
+from io import BytesIO
from onegov.gazette.tests import login_editor_1
from onegov.gazette.tests import login_publisher
+from PyPDF2 import PdfFileReader
from pyquery import PyQuery as pq
from webtest import TestApp as Client
@@ -47,23 +49,23 @@ def test_view_issues(gazette_app):
for tr in manage.pyquery('table.issues.past tbody tr')
]
assert upcoming_issues == [
- ['2017-44', '03.11.2017', 'Mittwoch 01.11.2017 13:00', ''],
- ['2017-45', '10.11.2017', 'Mittwoch 08.11.2017 13:00', ''],
- ['2017-46', '17.11.2017', 'Mittwoch 15.11.2017 13:00', ''],
- ['2017-47', '24.11.2017', 'Mittwoch 22.11.2017 13:00', ''],
- ['2017-48', '01.12.2017', 'Mittwoch 29.11.2017 13:00', ''],
- ['2017-49', '08.12.2017', 'Mittwoch 06.12.2017 13:00', ''],
- ['2017-50', '15.12.2017', 'Mittwoch 13.12.2017 13:00', ''],
- ['2017-51', '22.12.2017', 'Mittwoch 20.12.2017 13:00', ''],
- ['2017-52', '29.12.2017', 'Mittwoch 27.12.2017 13:00', ''],
- ['2018-1', '05.01.2018', 'Mittwoch 03.01.2018 13:00', ''],
- ['2019-1', '02.01.2019', 'Dienstag 01.01.2019 12:00', '']
+ ['2017-44', '03.11.2017', 'Mittwoch 01.11.2017 13:00', '', ''],
+ ['2017-45', '10.11.2017', 'Mittwoch 08.11.2017 13:00', '', ''],
+ ['2017-46', '17.11.2017', 'Mittwoch 15.11.2017 13:00', '', ''],
+ ['2017-47', '24.11.2017', 'Mittwoch 22.11.2017 13:00', '', ''],
+ ['2017-48', '01.12.2017', 'Mittwoch 29.11.2017 13:00', '', ''],
+ ['2017-49', '08.12.2017', 'Mittwoch 06.12.2017 13:00', '', ''],
+ ['2017-50', '15.12.2017', 'Mittwoch 13.12.2017 13:00', '', ''],
+ ['2017-51', '22.12.2017', 'Mittwoch 20.12.2017 13:00', '', ''],
+ ['2017-52', '29.12.2017', 'Mittwoch 27.12.2017 13:00', '', ''],
+ ['2018-1', '05.01.2018', 'Mittwoch 03.01.2018 13:00', '', ''],
+ ['2019-1', '02.01.2019', 'Dienstag 01.01.2019 12:00', '', '']
]
assert past_issues == [
- ['2017-43', '27.10.2017', 'Mittwoch 25.10.2017 14:00', ''],
- ['2017-42', '20.10.2017', 'Mittwoch 18.10.2017 14:00', ''],
- ['2017-41', '13.10.2017', 'Mittwoch 11.10.2017 14:00', ''],
- ['2017-40', '06.10.2017', 'Mittwoch 04.10.2017 14:00', ''],
+ ['2017-43', '27.10.2017', 'Mittwoch 25.10.2017 14:00', '', ''],
+ ['2017-42', '20.10.2017', 'Mittwoch 18.10.2017 14:00', '', ''],
+ ['2017-41', '13.10.2017', 'Mittwoch 11.10.2017 14:00', '', ''],
+ ['2017-40', '06.10.2017', 'Mittwoch 04.10.2017 14:00', '', ''],
]
# use the first available issue in a notice
@@ -94,23 +96,23 @@ def test_view_issues(gazette_app):
for tr in manage.pyquery('table.issues.past tbody tr')
]
assert upcoming_issues == [
- ['2017-44', '02.11.2017', 'Mittwoch 01.11.2017 12:00', ''],
- ['2017-45', '10.11.2017', 'Mittwoch 08.11.2017 13:00', ''],
- ['2017-46', '17.11.2017', 'Mittwoch 15.11.2017 13:00', ''],
- ['2017-47', '24.11.2017', 'Mittwoch 22.11.2017 13:00', ''],
- ['2017-48', '01.12.2017', 'Mittwoch 29.11.2017 13:00', ''],
- ['2017-49', '08.12.2017', 'Mittwoch 06.12.2017 13:00', ''],
- ['2017-50', '15.12.2017', 'Mittwoch 13.12.2017 13:00', ''],
- ['2017-51', '22.12.2017', 'Mittwoch 20.12.2017 13:00', ''],
- ['2017-52', '29.12.2017', 'Mittwoch 27.12.2017 13:00', ''],
- ['2018-1', '05.01.2018', 'Mittwoch 03.01.2018 13:00', ''],
- ['2019-1', '02.01.2019', 'Dienstag 01.01.2019 12:00', '']
+ ['2017-44', '02.11.2017', 'Mittwoch 01.11.2017 12:00', '', ''],
+ ['2017-45', '10.11.2017', 'Mittwoch 08.11.2017 13:00', '', ''],
+ ['2017-46', '17.11.2017', 'Mittwoch 15.11.2017 13:00', '', ''],
+ ['2017-47', '24.11.2017', 'Mittwoch 22.11.2017 13:00', '', ''],
+ ['2017-48', '01.12.2017', 'Mittwoch 29.11.2017 13:00', '', ''],
+ ['2017-49', '08.12.2017', 'Mittwoch 06.12.2017 13:00', '', ''],
+ ['2017-50', '15.12.2017', 'Mittwoch 13.12.2017 13:00', '', ''],
+ ['2017-51', '22.12.2017', 'Mittwoch 20.12.2017 13:00', '', ''],
+ ['2017-52', '29.12.2017', 'Mittwoch 27.12.2017 13:00', '', ''],
+ ['2018-1', '05.01.2018', 'Mittwoch 03.01.2018 13:00', '', ''],
+ ['2019-1', '02.01.2019', 'Dienstag 01.01.2019 12:00', '', '']
]
assert past_issues == [
- ['2017-43', '27.10.2017', 'Mittwoch 25.10.2017 14:00', ''],
- ['2017-42', '20.10.2017', 'Mittwoch 18.10.2017 14:00', ''],
- ['2017-41', '13.10.2017', 'Mittwoch 11.10.2017 14:00', ''],
- ['2017-40', '06.10.2017', 'Mittwoch 04.10.2017 14:00', ''],
+ ['2017-43', '27.10.2017', 'Mittwoch 25.10.2017 14:00', '', ''],
+ ['2017-42', '20.10.2017', 'Mittwoch 18.10.2017 14:00', '', ''],
+ ['2017-41', '13.10.2017', 'Mittwoch 11.10.2017 14:00', '', ''],
+ ['2017-40', '06.10.2017', 'Mittwoch 04.10.2017 14:00', '', ''],
]
# check if the notice has been updated
@@ -141,7 +143,7 @@ def test_view_issues(gazette_app):
for tr in manage.pyquery('table.issues tbody tr')
]
assert issues == [
- ['2017-44', '02.11.2017', 'Mittwoch 01.11.2017 12:00', '']
+ ['2017-44', '02.11.2017', 'Mittwoch 01.11.2017 12:00', '', '']
]
# Try to delete the used issue
@@ -168,3 +170,127 @@ def test_view_issues_permissions(gazette_app):
client.get('/issues', status=403)
client.get(edit_link, status=403)
client.get(delete_link, status=403)
+
+
+def test_view_issues_publish(gazette_app):
+ with freeze_time("2017-11-01 12:00"):
+ client = Client(gazette_app)
+ login_publisher(client)
+
+ for number, issues in enumerate(((44, 45), (45, 46), (45,))):
+ slug = 'notice-{}'.format(number)
+ manage = client.get('/notices/drafted/new-notice')
+ manage.form['title'] = slug
+ manage.form['organization'] = '200'
+ manage.form['category'] = '13'
+ manage.form['issues'] = ['2017-{}'.format(i) for i in issues]
+ manage.form['text'] = 'Text'
+ manage = manage.form.submit()
+
+ client.get('/notice/{}/submit'.format(slug)).form.submit()
+ if len(issues) > 1:
+ client.get('/notice/{}/accept'.format(slug)).form.submit()
+
+ # publish 44
+ manage = client.get('/issues').click('Veröffentlichen', index=0)
+ assert "Publikationsnummern für 1 Meldung(en) vergeben." in manage
+ manage.form.submit()
+
+ notice_0 = client.get('/notice/notice-0')
+ notice_1 = client.get('/notice/notice-1')
+ notice_2 = client.get('/notice/notice-2')
+ assert '- Nr. 44, 03.11.2017 / 1
' in notice_0
+ assert '- Nr. 45, 10.11.2017 / 2
' in notice_0
+ assert '- Nr. 45, 10.11.2017
' in notice_1
+ assert '- Nr. 46, 17.11.2017
' in notice_1
+ assert '- Nr. 45, 10.11.2017
' in notice_2
+
+ # publish 45
+ manage = client.get('/issues').click('Veröffentlichen', index=1)
+ assert "Diese Ausgabe hat eingereichte Meldungen!" in manage
+ assert "Publikationsnummern für 1 Meldung(en) vergeben." in manage
+ manage.form.submit()
+
+ notice_0 = client.get('/notice/notice-0')
+ notice_1 = client.get('/notice/notice-1')
+ notice_2 = client.get('/notice/notice-2')
+ assert '- Nr. 44, 03.11.2017 / 1
' in notice_0
+ assert '- Nr. 45, 10.11.2017 / 2
' in notice_0
+ assert '- Nr. 45, 10.11.2017 / 3
' in notice_1
+ assert '- Nr. 46, 17.11.2017 / 4
' in notice_1
+ assert '- Nr. 45, 10.11.2017
' in notice_2
+
+
+def test_view_issues_generate(gazette_app):
+ with freeze_time("2017-11-01 12:00"):
+ client = Client(gazette_app)
+ login_publisher(client)
+
+ # add a notice
+ manage = client.get('/notices/drafted/new-notice')
+ manage.form['title'] = 'First notice'
+ manage.form['organization'] = '200'
+ manage.form['category'] = '13'
+ manage.form['issues'] = ['2017-44']
+ manage.form['text'] = 'This is the first notice'
+ manage = manage.form.submit()
+
+ client.get('/notice/first-notice/submit').form.submit()
+ client.get('/notice/first-notice/accept').form.submit()
+ client.get('/notice/first-notice/publish').form.submit()
+
+ # generate 44
+ manage = client.get('/issues').click('Erzeugen', index=0)
+ manage = manage.form.submit().maybe_follow()
+ assert "PDF erstellt." in manage
+ assert "2017-44.pdf" in manage
+
+ manage = manage.click('2017-44.pdf')
+ assert manage.content_type == 'application/pdf'
+
+ reader = PdfFileReader(BytesIO(manage.body))
+ text = ''.join([page.extractText() for page in reader.pages])
+ assert text == (
+ '© 2017 Govikon\n'
+ '1\nAmtsblatt Nr. 44, 03.11.2017\n'
+ 'Civic Community\n'
+ 'Commercial Register\n'
+ 'First notice 1\n'
+ 'This is the first notice\n'
+ )
+
+ # add another notice
+ manage = client.get('/notices/drafted/new-notice')
+ manage.form['title'] = 'Second notice'
+ manage.form['organization'] = '100'
+ manage.form['category'] = '11'
+ manage.form['issues'] = ['2017-44']
+ manage.form['text'] = 'This is the second notice'
+ manage = manage.form.submit()
+
+ client.get('/notice/second-notice/submit').form.submit()
+ client.get('/notice/second-notice/accept').form.submit()
+ client.get('/notice/second-notice/publish').form.submit()
+
+ # generate again
+ manage = client.get('/issues').click('Erzeugen', index=0)
+ manage = manage.form.submit().maybe_follow()
+ assert "PDF erstellt." in manage
+
+ manage = manage.click('2017-44.pdf')
+ assert manage.content_type == 'application/pdf'
+
+ reader = PdfFileReader(BytesIO(manage.body))
+ text = ''.join([page.extractText() for page in reader.pages])
+ assert text == (
+ '© 2017 Govikon\n'
+ '1\nAmtsblatt Nr. 44, 03.11.2017\n'
+ 'State Chancellery\n'
+ 'Education\n'
+ 'Second notice 2\n'
+ 'This is the second notice\n'
+ 'Civic Community\n'
+ 'Commercial Register\n'
+ 'First notice 1\n'
+ 'This is the first notice\n'
+ )
diff --git a/onegov/gazette/tests/test_views_notice.py b/onegov/gazette/tests/test_views_notice.py
index 8367ccc..2de5ddd 100644
--- a/onegov/gazette/tests/test_views_notice.py
+++ b/onegov/gazette/tests/test_views_notice.py
@@ -2,11 +2,12 @@
from onegov.gazette.tests import accept_notice
from onegov.gazette.tests import edit_notice
from onegov.gazette.tests import login_users
+from onegov.gazette.tests import publish_notice
from onegov.gazette.tests import reject_notice
from onegov.gazette.tests import submit_notice
-def test_view_notice(gazette_app):
+def test_view_notice1(gazette_app):
# Check if the details of the notice is displayed correctly in the
# display view (that is: organization, owner, group etc).
@@ -49,6 +50,23 @@ def test_view_notice(gazette_app):
assert "in Arbeit" in view
assert "erstellt" in view
+ # Check if the publication numbers are displayed
+ submit_notice(editor_1, 'titel-1')
+ submit_notice(editor_2, 'titel-2')
+ submit_notice(editor_3, 'titel-3')
+ accept_notice(publisher, 'titel-1')
+ accept_notice(publisher, 'titel-2')
+ accept_notice(publisher, 'titel-3')
+ publish_notice(publisher, 'titel-1')
+ publish_notice(publisher, 'titel-2')
+ publish_notice(publisher, 'titel-3')
+
+ for number in range(3):
+ for user in (editor_1, editor_2, editor_3, publisher):
+ view = user.get('/notice/titel-{}'.format(number + 1))
+ assert "Nr. 44, 03.11.2017 / {}".format(2 * number + 1) in view
+ assert "Nr. 45, 10.11.2017 / {}".format(2 * number + 2) in view
+
def test_view_notice_actions(gazette_app):
# Check if the actions are displayed correctly in the detail view
@@ -64,7 +82,7 @@ def test_view_notice_actions(gazette_app):
manage.form['title'] = 'Titel {}'.format(count + 1)
manage.form['organization'] = '200'
manage.form['category'] = '11'
- manage.form['issues'] = ['2017-44', '2017-45']
+ manage.form['issues'] = ['2017-44']
manage.form['text'] = "1. Oktober 2017"
manage.form.submit()
@@ -78,6 +96,7 @@ def test_view_notice_actions(gazette_app):
's': 'action-submit',
'a': 'action-accept',
'r': 'action-reject',
+ 'x': 'action-publish'
}
def check(values):
@@ -180,10 +199,38 @@ def check(values):
accept_notice(publisher, 'titel-4')
check((
- (admin, 'titel-1', 'ptedc'),
- (admin, 'titel-2', 'ptedc'),
- (admin, 'titel-3', 'ptedc'),
- (admin, 'titel-4', 'ptedc'),
+ (admin, 'titel-1', 'ptedcx'),
+ (admin, 'titel-2', 'ptedcx'),
+ (admin, 'titel-3', 'ptedcx'),
+ (admin, 'titel-4', 'ptedcx'),
+ (publisher, 'titel-1', 'pcx'),
+ (publisher, 'titel-2', 'pcx'),
+ (publisher, 'titel-3', 'pcx'),
+ (publisher, 'titel-4', 'pcx'),
+ (editor_1, 'titel-1', 'pc'),
+ (editor_1, 'titel-2', 'pc'),
+ (editor_1, 'titel-3', 'pc'),
+ (editor_1, 'titel-4', 'pc'),
+ (editor_2, 'titel-1', 'pc'),
+ (editor_2, 'titel-2', 'pc'),
+ (editor_2, 'titel-3', 'pc'),
+ (editor_2, 'titel-4', 'pc'),
+ (editor_3, 'titel-1', 'pc'),
+ (editor_3, 'titel-2', 'pc'),
+ (editor_3, 'titel-3', 'pc'),
+ (editor_3, 'titel-4', 'pc'),
+ ))
+
+ # ... when published
+ publish_notice(publisher, 'titel-1')
+ publish_notice(publisher, 'titel-2')
+ publish_notice(publisher, 'titel-3')
+ publish_notice(publisher, 'titel-4')
+ check((
+ (admin, 'titel-1', 'ptec'),
+ (admin, 'titel-2', 'ptec'),
+ (admin, 'titel-3', 'ptec'),
+ (admin, 'titel-4', 'ptec'),
(publisher, 'titel-1', 'pc'),
(publisher, 'titel-2', 'pc'),
(publisher, 'titel-3', 'pc'),
@@ -300,6 +347,23 @@ def test_view_notice_delete(gazette_app):
assert "Diese Meldung wurde bereits angenommen!" in manage
manage.form.submit().maybe_follow()
+ # delete a published notice
+ manage = editor_1.get('/notices/drafted/new-notice')
+ manage.form['title'] = "Erneuerungswahlen"
+ manage.form['organization'] = '200'
+ manage.form['category'] = '11'
+ manage.form['issues'] = ['2017-44', '2017-45']
+ manage.form['text'] = "1. Oktober 2017"
+ manage.form.submit()
+
+ submit_notice(editor_1, 'erneuerungswahlen')
+ accept_notice(publisher, 'erneuerungswahlen')
+ publish_notice(publisher, 'erneuerungswahlen')
+
+ for user in (admin, editor_1, publisher):
+ manage = user.get('/notice/erneuerungswahlen/delete')
+ assert manage.forms == {}
+
def test_view_notice_changelog(gazette_app):
admin, editor_1, editor_2, editor_3, publisher = login_users(gazette_app)
@@ -328,6 +392,9 @@ def test_view_notice_changelog(gazette_app):
with freeze_time("2017-11-01 15:00"):
accept_notice(publisher, 'erneuerungswahlen')
+ with freeze_time("2017-11-01 16:00"):
+ publish_notice(publisher, 'erneuerungswahlen')
+
view = editor_1.get('/notice/erneuerungswahlen')
changes = [
@@ -354,6 +421,7 @@ def test_view_notice_changelog(gazette_app):
'eingereicht'),
('01.11.2017 16:00', 'Publisher', '', 'Druck beauftragt'),
('01.11.2017 16:00', 'Publisher', '', 'angenommen'),
+ ('01.11.2017 17:00', 'Publisher', '', 'veröffentlicht')
]
diff --git a/onegov/gazette/tests/test_views_notice_states.py b/onegov/gazette/tests/test_views_notice_states.py
index 18c6c46..abb5982 100644
--- a/onegov/gazette/tests/test_views_notice_states.py
+++ b/onegov/gazette/tests/test_views_notice_states.py
@@ -3,6 +3,7 @@
from onegov.gazette.tests import change_category
from onegov.gazette.tests import change_organization
from onegov.gazette.tests import login_users
+from onegov.gazette.tests import publish_notice
from onegov.gazette.tests import reject_notice
from onegov.gazette.tests import submit_notice
@@ -202,3 +203,33 @@ def test_view_notice_accept(gazette_app):
payload = message.get_payload(1).get_payload(decode=True)
payload = payload.decode('utf-8')
assert '44 xxx Titel 3' in payload
+
+
+def test_view_notice_publish(gazette_app):
+ admin, editor_1, editor_2, editor_3, publisher = login_users(gazette_app)
+
+ with freeze_time("2017-11-01 11:00"):
+ # create a notice for each editor
+ for count, user in enumerate((editor_1, editor_2, editor_3)):
+ manage = user.get('/notices/drafted/new-notice')
+ manage.form['title'] = 'Titel {}'.format(count + 1)
+ manage.form['organization'] = '410'
+ manage.form['category'] = '11'
+ manage.form['issues'] = ['2017-44', '2017-45']
+ manage.form['text'] = "1. Oktober 2017"
+ manage.form.submit()
+ submit_notice(user, 'titel-{}'.format(count + 1))
+ accept_notice(publisher, 'titel-{}'.format(count + 1))
+
+ with freeze_time("2017-11-01 15:00"):
+ # check if the notices can be published
+ for user, slug, forbidden in (
+ (editor_1, 'titel-1', True),
+ (editor_1, 'titel-2', True),
+ (editor_1, 'titel-3', True),
+ (editor_3, 'titel-3', True),
+ (publisher, 'titel-1', False),
+ (publisher, 'titel-2', False),
+ (publisher, 'titel-3', False),
+ ):
+ publish_notice(user, slug, forbidden=forbidden)
diff --git a/onegov/gazette/tests/test_views_notices.py b/onegov/gazette/tests/test_views_notices.py
index 9804189..33d1ec4 100644
--- a/onegov/gazette/tests/test_views_notices.py
+++ b/onegov/gazette/tests/test_views_notices.py
@@ -29,7 +29,9 @@ def test_view_notices(gazette_app):
login_editor_3(editor_3)
for user in (publisher, editor_1, editor_2, editor_3):
- for state in ('drafted', 'submitted', 'rejected', 'accepted'):
+ for state in (
+ 'drafted', 'submitted', 'rejected', 'accepted', 'published'
+ ):
assert "Keine Meldungen" in user.get('/notices/' + state)
# new notices
@@ -50,7 +52,7 @@ def test_view_notices(gazette_app):
manage.form.submit()
for user in (publisher, editor_1, editor_2, editor_3):
- for state in ('submitted', 'rejected', 'accepted'):
+ for state in ('submitted', 'rejected', 'accepted', 'published'):
assert "Keine Meldungen" in user.get('/notices/' + state)
assert "Erneuerungswahlen" in publisher.get('/notices/drafted')
@@ -67,7 +69,7 @@ def test_view_notices(gazette_app):
editor_3.get('/notice/kantonsratswahlen/submit').form.submit()
for user in (publisher, editor_1, editor_2, editor_3):
- for state in ('drafted', 'rejected', 'accepted'):
+ for state in ('drafted', 'rejected', 'accepted', 'published'):
assert "Keine Meldungen" in user.get('/notices/' + state)
assert "Erneuerungswahlen" in publisher.get('/notices/submitted')
@@ -89,7 +91,7 @@ def test_view_notices(gazette_app):
manage.form.submit()
for user in (publisher, editor_1, editor_2, editor_3):
- for state in ('drafted', 'submitted', 'accepted'):
+ for state in ('drafted', 'submitted', 'accepted', 'published'):
assert "Keine Meldungen" in user.get('/notices/' + state)
assert "Erneuerungswahlen" in publisher.get('/notices/rejected')
@@ -108,7 +110,7 @@ def test_view_notices(gazette_app):
publisher.get('/notice/kantonsratswahlen/accept').form.submit()
for user in (publisher, editor_1, editor_2, editor_3):
- for state in ('drafted', 'submitted', 'rejected'):
+ for state in ('drafted', 'submitted', 'rejected', 'published'):
assert "Keine Meldungen" in user.get('/notices/' + state)
assert "Erneuerungswahlen" in publisher.get('/notices/accepted')
@@ -120,6 +122,16 @@ def test_view_notices(gazette_app):
assert "Kantonsratswahlen" not in editor_2.get('/notices/accepted')
assert "Kantonsratswahlen" in editor_3.get('/notices/accepted')
+ # publish notices
+ assert "Erneuerungswahlen" in publisher.get('/notices/accepted')
+ assert "Erneuerungswahlen" in editor_1.get('/notices/accepted')
+ assert "Erneuerungswahlen" in editor_2.get('/notices/accepted')
+ assert "Erneuerungswahlen" not in editor_3.get('/notices/accepted')
+ assert "Kantonsratswahlen" in publisher.get('/notices/accepted')
+ assert "Kantonsratswahlen" not in editor_1.get('/notices/accepted')
+ assert "Kantonsratswahlen" not in editor_2.get('/notices/accepted')
+ assert "Kantonsratswahlen" in editor_3.get('/notices/accepted')
+
def test_view_notices_search(gazette_app):
with freeze_time("2017-11-01 11:00"):
@@ -481,6 +493,7 @@ def statistic(state, sheet_name, qs=None):
assert publisher.get('/notices/drafted/statistics')
assert publisher.get('/notices/submitted/statistics')
+ assert publisher.get('/notices/published/statistics')
# organizations/drafted: 5 x 100, 3 x 200
assert statistic('drafted', 'Organisationen') == [
diff --git a/onegov/gazette/tests/test_views_principal.py b/onegov/gazette/tests/test_views_principal.py
index a709a6a..4afe9f4 100644
--- a/onegov/gazette/tests/test_views_principal.py
+++ b/onegov/gazette/tests/test_views_principal.py
@@ -1,13 +1,16 @@
+from freezegun import freeze_time
from onegov.gazette.tests import login_admin
from onegov.gazette.tests import login_editor_1
from onegov.gazette.tests import login_publisher
+from pyquery import PyQuery as pq
from webtest import TestApp as Client
def test_view_principal(gazette_app):
client = Client(gazette_app)
- assert 'auth/login' in client.get('/').maybe_follow().request.url
+ assert 'Startseite' in client.get('/').maybe_follow()
+ assert 'Anmelden' in client.get('/').maybe_follow()
login_admin(client)
assert '/notices' in client.get('/').maybe_follow().request.url
@@ -19,6 +22,86 @@ def test_view_principal(gazette_app):
assert '/dashboard' in client.get('/').maybe_follow().request.url
+def test_view_archive(gazette_app):
+ principal = gazette_app.principal
+ principal.show_archive = True
+ gazette_app.cache.set('principal', principal)
+
+ with freeze_time("2017-11-01 12:00"):
+ client = Client(gazette_app)
+
+ publisher = Client(gazette_app)
+ login_publisher(publisher)
+
+ # generate past issues
+ for idx in range(13, 9, -1):
+ manage = publisher.get('/issues').click('Erzeugen', index=idx)
+ manage = manage.form.submit().maybe_follow()
+ assert "PDF erstellt." in manage
+
+ archive = client.get('/').maybe_follow()
+ assert "2017" in archive
+ assert "2018" not in archive
+
+ issues = pq(archive.body)('li a')
+ assert [a.text for a in issues] == [
+ 'Nr. 43, 27.10.2017',
+ 'Nr. 42, 20.10.2017',
+ 'Nr. 41, 13.10.2017',
+ 'Nr. 40, 06.10.2017'
+ ]
+ assert [a.attrib['href'] for a in issues] == [
+ 'http://localhost/pdf/2017-43.pdf',
+ 'http://localhost/pdf/2017-42.pdf',
+ 'http://localhost/pdf/2017-41.pdf',
+ 'http://localhost/pdf/2017-40.pdf'
+ ]
+
+ # publish the generate
+ for idx in range(0, 10):
+ manage = publisher.get('/issues').click('Erzeugen', index=idx)
+ manage = manage.form.submit().maybe_follow()
+ assert "PDF erstellt." in manage
+
+ archive = client.get('/').maybe_follow()
+ assert "2017" in archive
+ assert "2018" in archive
+
+ issues = pq(archive.body)('li a')
+ assert [a.text for a in issues] == [
+ 'Nr. 1, 05.01.2018',
+ 'Nr. 52, 29.12.2017',
+ 'Nr. 51, 22.12.2017',
+ 'Nr. 50, 15.12.2017',
+ 'Nr. 49, 08.12.2017',
+ 'Nr. 48, 01.12.2017',
+ 'Nr. 47, 24.11.2017',
+ 'Nr. 46, 17.11.2017',
+ 'Nr. 45, 10.11.2017',
+ 'Nr. 44, 03.11.2017',
+ 'Nr. 43, 27.10.2017',
+ 'Nr. 42, 20.10.2017',
+ 'Nr. 41, 13.10.2017',
+ 'Nr. 40, 06.10.2017'
+ ]
+ assert [a.attrib['href'] for a in issues] == [
+ 'http://localhost/pdf/2018-1.pdf',
+ 'http://localhost/pdf/2017-52.pdf',
+ 'http://localhost/pdf/2017-51.pdf',
+ 'http://localhost/pdf/2017-50.pdf',
+ 'http://localhost/pdf/2017-49.pdf',
+ 'http://localhost/pdf/2017-48.pdf',
+ 'http://localhost/pdf/2017-47.pdf',
+ 'http://localhost/pdf/2017-46.pdf',
+ 'http://localhost/pdf/2017-45.pdf',
+ 'http://localhost/pdf/2017-44.pdf',
+ 'http://localhost/pdf/2017-43.pdf',
+ 'http://localhost/pdf/2017-42.pdf',
+ 'http://localhost/pdf/2017-41.pdf',
+ 'http://localhost/pdf/2017-40.pdf'
+ ]
+
+
def test_view_help_link(gazette_app):
client = Client(gazette_app)
diff --git a/onegov/gazette/theme/styles/gazette.scss b/onegov/gazette/theme/styles/gazette.scss
index 25024e6..8a5c2be 100644
--- a/onegov/gazette/theme/styles/gazette.scss
+++ b/onegov/gazette/theme/styles/gazette.scss
@@ -593,6 +593,12 @@ a.button.right {
margin-right: .75ex;
}
+.action-generate::before {
+ content: '\f1c1';
+ font-family: 'FontAwesome';
+ margin-right: .75ex;
+}
+
.action-order::before {
content: '\f047';
font-family: 'FontAwesome';
@@ -611,6 +617,12 @@ a.button.right {
margin-right: .75ex;
}
+.action-publish::before {
+ content: '\f00c';
+ font-family: 'FontAwesome';
+ margin-right: .75ex;
+}
+
.action-reject::before {
content: '\f05e';
font-family: 'FontAwesome';
diff --git a/onegov/gazette/views/issues.py b/onegov/gazette/views/issues.py
index 75c9bc0..443d5e4 100644
--- a/onegov/gazette/views/issues.py
+++ b/onegov/gazette/views/issues.py
@@ -8,6 +8,7 @@
from onegov.gazette.forms import IssueForm
from onegov.gazette.layout import Layout
from onegov.gazette.models import Issue
+from onegov.gazette.pdf import Pdf
@GazetteApp.html(
@@ -150,3 +151,81 @@ def delete_issue(self, request, form):
'button_class': 'alert',
'cancel': layout.manage_issues_link
}
+
+
+@GazetteApp.form(
+ model=Issue,
+ name='publish',
+ template='form.pt',
+ permission=Private,
+ form=EmptyForm
+)
+def publish_issue(self, request, form):
+ """ Publish an issue.
+
+ If the issue has not already been published before, we redirect to the
+ PDF generation view afterwards.
+
+ This view is only visible by a publisher.
+
+ """
+
+ if self.notices('submitted').first():
+ request.message(
+ _("There are submitted notices for this issue!"), 'warning'
+ )
+
+ layout = Layout(self, request)
+ if form.submitted(request):
+ self.publish(request)
+ request.message(_("All notices published."), 'success')
+ return redirect(request.link(self, name='generate'))
+
+ return {
+ 'layout': layout,
+ 'form': form,
+ 'title': self.name,
+ 'subtitle': _("Publish all notices"),
+ 'button_text': _("Publish"),
+ 'cancel': layout.manage_issues_link,
+ 'message': _(
+ (
+ 'Do you really want to publish all notices of "${item}"? This '
+ 'will assign the publication numbers for ${number} notice(s).'
+ ),
+ mapping={
+ 'item': self.name,
+ 'number': len(self.notices('accepted').all())
+ }
+ ),
+ }
+
+
+@GazetteApp.form(
+ model=Issue,
+ name='generate',
+ template='form.pt',
+ permission=Private,
+ form=EmptyForm
+)
+def generate_issue(self, request, form):
+ """ Generates the PDF of the issue.
+
+ This view is only visible by a publisher.
+
+ """
+
+ layout = Layout(self, request)
+ if form.submitted(request):
+ self.pdf = Pdf.from_issue(self, request)
+ request.message(_("PDF generated."), 'success')
+ return redirect(layout.manage_issues_link)
+
+ return {
+ 'layout': layout,
+ 'form': form,
+ 'title': self.name,
+ 'subtitle': _("Generate PDF"),
+ 'button_text': _("Generate"),
+ 'cancel': layout.manage_issues_link,
+ }
diff --git a/onegov/gazette/views/notice.py b/onegov/gazette/views/notice.py
index 02d7e36..569b702 100644
--- a/onegov/gazette/views/notice.py
+++ b/onegov/gazette/views/notice.py
@@ -79,11 +79,19 @@ def _action(label, name, class_, target='_self'):
if admin:
actions.append(action['delete'])
elif self.state == 'accepted':
+ if publisher:
+ actions.append(action['publish'])
actions.append(action['copy'])
if admin:
actions.append(action['edit_un'])
actions.append(action['attachments'])
actions.append(action['delete'])
+ elif self.state == 'published':
+ actions.append(action['copy'])
+ if admin:
+ actions.append(action['edit_un'])
+ actions.append(action['attachments'])
+
actions.append(action['preview'])
return {
@@ -214,6 +222,15 @@ def edit_notice_unrestricted(self, request, form):
layout = Layout(self, request)
+ if self.state == 'published':
+ form.disable_issues()
+
+ if form.submitted(request):
+ form.update_model(self)
+ self.add_change(request, _("edited"))
+ request.message(_("Official notice modified."), 'success')
+ return redirect(request.link(self))
+
if self.state == 'accepted':
request.message(
_("This official notice has already been accepted!"), 'warning'
@@ -223,12 +240,6 @@ def edit_notice_unrestricted(self, request, form):
_("This official notice has already been published!"), 'warning'
)
- if form.submitted(request):
- form.update_model(self)
- self.add_change(request, _("edited"))
- request.message(_("Official notice modified."), 'success')
- return redirect(request.link(self))
-
if not form.errors:
form.apply_model(self)
diff --git a/onegov/gazette/views/notice_states.py b/onegov/gazette/views/notice_states.py
index e1bd95c..08b0461 100644
--- a/onegov/gazette/views/notice_states.py
+++ b/onegov/gazette/views/notice_states.py
@@ -258,3 +258,55 @@ def reject_notice(self, request, form):
'button_class': 'alert',
'cancel': request.link(self)
}
+
+
+@GazetteApp.form(
+ model=GazetteNotice,
+ name='publish',
+ template='form.pt',
+ permission=Private,
+ form=EmptyForm
+)
+def publish_notice(self, request, form):
+ """ Publish a notice.
+
+ This view is used by the publishers to publish an accepted notice.
+
+ Only accepted notices may be published.
+
+ """
+
+ layout = Layout(self, request)
+
+ if self.state != 'accepted':
+ return {
+ 'layout': layout,
+ 'title': self.title,
+ 'subtitle': _("Publish Official Note"),
+ 'callout': _("Only accepted official notices may be published."),
+ 'show_form': False
+ }
+
+ if form.submitted(request):
+ self.publish(request)
+ request.message(_("Official notice published."), 'success')
+ return redirect(layout.dashboard_or_notices_link)
+
+ return {
+ 'message': _(
+ (
+ 'Do you really want to publish "${item}"? This will assign '
+ 'the publication numbers for the following issues: ${issues}.'
+ ),
+ mapping={
+ 'item': self.title,
+ 'issues': ', '.join(self.issues.keys())
+ }
+ ),
+ 'layout': layout,
+ 'form': form,
+ 'title': self.title,
+ 'subtitle': _("Publish Official Note"),
+ 'button_text': _("Publish Official Note"),
+ 'cancel': request.link(self)
+ }
diff --git a/onegov/gazette/views/notices.py b/onegov/gazette/views/notices.py
index fe06a6c..b408053 100644
--- a/onegov/gazette/views/notices.py
+++ b/onegov/gazette/views/notices.py
@@ -86,7 +86,9 @@ def view_notices(self, request):
'link': request.link(self.for_state(state)),
'class': 'active' if state == self.state else ''
}
- for state in ('drafted', 'submitted', 'accepted', 'rejected')
+ for state in (
+ 'drafted', 'submitted', 'accepted', 'rejected', 'published'
+ )
)
orderings = {
@@ -126,7 +128,7 @@ def view_notices(self, request):
if not request.is_private(self):
self.user_ids, self.group_ids = get_user_and_group(request)
filters = None
- title = _("My Accepted Official Notices")
+ title = _("My Published Official Notices")
return {
'layout': layout,
@@ -164,7 +166,9 @@ def view_notices_statistics(self, request):
'link': request.link(self.for_state(state), name='statistics'),
'class': 'active' if state == self.state else ''
}
- for state in ('drafted', 'submitted', 'accepted', 'rejected')
+ for state in (
+ 'drafted', 'submitted', 'accepted', 'rejected', 'published'
+ )
)
return {
diff --git a/onegov/gazette/views/principal.py b/onegov/gazette/views/principal.py
index 39d9d3f..c62da91 100644
--- a/onegov/gazette/views/principal.py
+++ b/onegov/gazette/views/principal.py
@@ -5,6 +5,7 @@
from onegov.gazette import _
from onegov.gazette import GazetteApp
from onegov.gazette.collections import GazetteNoticeCollection
+from onegov.gazette.collections import IssueCollection
from onegov.gazette.layout import Layout
from onegov.gazette.models import Principal
from onegov.gazette.views import get_user_and_group
@@ -13,11 +14,15 @@
@GazetteApp.html(
model=Principal,
- permission=Public
+ permission=Public,
+ template='archive.pt',
)
def view_principal(self, request):
- """ The homepage. Redirects to the default management views according to
- the logged in role.
+ """ The homepage.
+
+ Redirects to the default management views according to the logged in role.
+
+ Shows the weekly PDFs if not logged-in.
"""
@@ -29,7 +34,15 @@ def view_principal(self, request):
if request.is_personal(self):
return redirect(layout.dashboard_link)
- return redirect(layout.login_link)
+ if not request.app.principal.show_archive:
+ return redirect(layout.login_link)
+
+ issues = IssueCollection(request.app.session()).by_years(desc=True)
+ return {
+ 'layout': layout,
+ 'title': "{} {}".format(_("Gazette"), request.app.principal.name),
+ 'issues': issues
+ }
@GazetteApp.html(
diff --git a/setup.py b/setup.py
index f8d20f0..9b87d7a 100644
--- a/setup.py
+++ b/setup.py
@@ -37,9 +37,11 @@ def get_long_description():
'cssmin',
'onegov.chat',
'onegov.core>=0.54.3',
+ 'onegov.file',
'onegov.form',
'onegov.foundation',
'onegov.notice>=0.3.0',
+ 'onegov.pdf>=0.3.0',
'onegov.quill>=0.2.3',
'onegov.shared',
'onegov.user>=0.17.0',
@@ -60,6 +62,7 @@ def get_long_description():
'pyquery',
'pytest-localserver',
'pytest',
+ 'PyPDF2',
'webtest',
],
),
| |