~homeworkprod/byceps

b5dc62d48db9823a9577f67bc65815116d0d3cfe — Jochen Kupperschmidt 9 months ago aab620e
Add admin UI to publish news item later
M byceps/blueprints/admin/news/forms.py => byceps/blueprints/admin/news/forms.py +6 -0
@@ 9,6 9,7 @@ byceps.blueprints.admin.news.forms
import re

from wtforms import FileField, StringField, TextAreaField
from wtforms.fields.html5 import DateField, TimeField
from wtforms.validators import InputRequired, Length, Optional, Regexp

from ....util.l10n import LocalizedForm


@@ 45,3 46,8 @@ class ItemCreateForm(LocalizedForm):

class ItemUpdateForm(ItemCreateForm):
    pass


class ItemPublishLaterForm(LocalizedForm):
    publish_on = DateField('Datum', [InputRequired()])
    publish_at = TimeField('Uhrzeit', [InputRequired()])

A byceps/blueprints/admin/news/templates/admin/news/item_publish_later_form.html => byceps/blueprints/admin/news/templates/admin/news/item_publish_later_form.html +28 -0
@@ 0,0 1,28 @@
{% extends 'layout/admin/base.html' %}
{% from 'macros/forms.html' import form_buttons, form_field, form_fieldset %}
{% from 'macros/icons.html' import render_icon %}
{% set current_page = 'news_admin' %}
{% set current_page_brand = item.brand %}
{% set title = 'News später veröffentlichen' %}

{% block body %}

  <nav class="breadcrumbs">
    <ol>
      <li>News</li>
      <li>Kanal <a href="{{ url_for('.channel_view', channel_id=item.channel.id) }}">{{ item.channel.id }}</a></li>
      <li>Newspost <a href="{{ url_for('.item_view', item_id=item.id) }}">{{ item.title }}</a></li>
    </ol>
  </nav>
  <h1>{{ render_icon('published') }} {{ title }}</h1>

  <form action="{{ url_for('.item_publish_later', item_id=item.id) }}" method="post">
    {% call form_fieldset() %}
      {{ form_field(form.publish_on, autofocus='autofocus') }}
      {{ form_field(form.publish_at, autofocus='autofocus') }}
    {% endcall %}

    {{ form_buttons('Veröffentlichen', cancel_url=url_for('.item_view', item_id=item.id)) }}
  </form>

{%- endblock %}

M byceps/blueprints/admin/news/templates/admin/news/item_view_version.html => byceps/blueprints/admin/news/templates/admin/news/item_view_version.html +3 -2
@@ 49,7 49,8 @@
        <ol class="dropdown-menu dropdown-menu--right">
          <li><a class="dropdown-item" href="{{ url_for('.image_create_form', item_id=item.id) }}">{{ render_icon('add') }} Bild hinzufügen</a></li>
          {%- if not item.published and g.current_user.has_permission(NewsItemPermission.publish) %}
          <li><a class="dropdown-item" data-action="item-publish" href="{{ url_for('.item_publish', item_id=item.id) }}">{{ render_icon('published') }} News veröffentlichen</a></li>
          <li><a class="dropdown-item" data-action="item-publish-now" href="{{ url_for('.item_publish_now', item_id=item.id) }}">{{ render_icon('published') }} News jetzt veröffentlichen</a></li>
          <li><a class="dropdown-item" href="{{ url_for('.item_publish_later_form', item_id=item.id) }}">{{ render_icon('published') }} News später veröffentlichen</a></li>
          {%- endif %}
        </ol>
      </div>


@@ 161,7 162,7 @@
{% block scripts %}
<script>
  onDomReady(function() {
    confirmed_post_on_click_then_reload('[data-action="item-publish"]', 'Newspost veröffentlichen?');
    confirmed_post_on_click_then_reload('[data-action="item-publish-now"]', 'Newspost jetzt veröffentlichen?');
  });
</script>
{% endblock %}

M byceps/blueprints/admin/news/views.py => byceps/blueprints/admin/news/views.py +46 -4
@@ 6,7 6,7 @@ byceps.blueprints.admin.news.views
:License: Revised BSD (see `LICENSE` file for details)
"""

from datetime import date
from datetime import date, datetime

from flask import abort, g, request



@@ 25,6 25,7 @@ from ....util.framework.blueprint import create_blueprint
from ....util.framework.flash import flash_success
from ....util.framework.templating import templated
from ....util.iterables import pairwise
from ....util.templatefilters import local_tz_to_utc
from ....util.views import permission_required, redirect_to, respond_no_content

from ...common.authorization.registry import permission_registry


@@ 35,6 36,7 @@ from .forms import (
    ImageCreateForm,
    ImageUpdateForm,
    ItemCreateForm,
    ItemPublishLaterForm,
    ItemUpdateForm,
)



@@ 436,11 438,51 @@ def item_update(item_id):
    return redirect_to('.item_view', item_id=item.id)


@blueprint.route('/items/<uuid:item_id>/publish', methods=['POST'])
@blueprint.route('/items/<uuid:item_id>/publish_later')
@permission_required(NewsItemPermission.publish)
@templated
def item_publish_later_form(item_id, erroneous_form=None):
    """Show form to publish a news item at a time in the future."""
    item = _get_item_or_404(item_id)

    form = erroneous_form if erroneous_form else ItemPublishLaterForm()

    return {
        'item': item,
        'form': form,
    }


@blueprint.route('/items/<uuid:item_id>/publish_later', methods=['POST'])
@permission_required(NewsItemPermission.publish)
def item_publish_later(item_id):
    """Publish a news item at a time in the future."""
    item = _get_item_or_404(item_id)

    form = ItemPublishLaterForm(request.form)
    if not form.validate():
        return item_publish_later_form(item.id, form)

    publish_at = local_tz_to_utc(
        datetime.combine(form.publish_on.data, form.publish_at.data)
    )

    event = news_item_service.publish_item(
        item.id, publish_at=publish_at, initiator_id=g.current_user.id
    )

    news_signals.item_published.send(None, event=event)

    flash_success(f'Die News "{item.title}" wird später veröffentlicht.')

    return redirect_to('.item_view', item_id=item.id)


@blueprint.route('/items/<uuid:item_id>/publish_now', methods=['POST'])
@permission_required(NewsItemPermission.publish)
@respond_no_content
def item_publish(item_id):
    """Publish a news item."""
def item_publish_now(item_id):
    """Publish a news item now."""
    item = _get_item_or_404(item_id)

    event = news_item_service.publish_item(

M byceps/services/news/models/item.py => byceps/services/news/models/item.py +5 -1
@@ 37,7 37,11 @@ class ItemQuery(BaseQuery):
        )

    def published(self) -> BaseQuery:
        """Return items that have been published."""
        """Return items that have been published and are public at this time.

        This excludes items that have been pre-published for a time that
        is still in the future.
        """
        return self.filter(Item.published_at <= datetime.utcnow())

    def with_current_version(self) -> BaseQuery:

M tests/integration/blueprints/admin/news/test_item_views.py => tests/integration/blueprints/admin/news/test_item_views.py +27 -2
@@ 82,13 82,38 @@ def test_update_form(news_admin_client, item):
    assert response.status_code == 200


def test_publish(news_admin_client, item):
def test_publish_later_form(news_admin_client, item):
    url = f'/admin/news/items/{item.id}/publish_later'
    response = news_admin_client.get(url)
    assert response.status_code == 200


def test_publish_later(news_admin_client, item):
    item_before = item_service.find_item(item.id)
    assert item_before.published_at is None
    assert not item_before.published

    url = f'/admin/news/items/{item.id}/publish_later'
    form_data = {
        'publish_on': '2021-01-23',
        'publish_at': '23:42',
    }
    response = news_admin_client.post(url, data=form_data)
    assert response.status_code == 302

    item_after = item_service.find_item(item.id)
    assert item_after.published_at is not None
    assert item_after.published


def test_publish_now(news_admin_client, item):
    item_before = item_service.find_item(item.id)
    assert item_before.published_at is None
    assert not item_before.published

    url = f'/admin/news/items/{item.id}/publish'
    url = f'/admin/news/items/{item.id}/publish_now'
    response = news_admin_client.post(url)
    assert response.status_code == 204

    item_after = item_service.find_item(item.id)
    assert item_after.published_at is not None