{{ event.title }}
{{ _("Start") }}: {{ event.start | datetimeformat }}
{{ _("End") }}: {{ event.end | datetimeformat }}
{% if event.location %}{{ event.location }}
{% endif %}PK ! ߗ abilian/__init__.py# coding=utf-8 from __future__ import absolute_import from pkgutil import extend_path # noinspection PyUnboundLocalVariable __path__ = extend_path(__path__, __name__) PK ! e abilian/sbe/__init__.py# coding=utf-8 PK ! } abilian/sbe/app.py# coding=utf-8 """Static configuration for the application. TODO: add more (runtime) flexibility in plugin discovery, selection and activation. """ from __future__ import absolute_import, division, print_function, \ unicode_literals import logging import os import subprocess import sys from pathlib import Path import jinja2 from abilian.app import Application as BaseApplication from abilian.core.celery import FlaskCelery as BaseCelery from abilian.core.celery import FlaskLoader as CeleryBaseLoader from abilian.core.commands import setup_abilian_commands from abilian.services import converter from flask import current_app from flask_script import Command, Manager from werkzeug.serving import BaseWSGIServer from .apps.documents.repository import repository from .extension import sbe # Used for side effects, do not remove __all__ = ["create_app", "Application"] logger = logging.getLogger(__name__) def create_app(config=None): return Application(config=config) command_manager = Manager(create_app) setup_abilian_commands(command_manager) # loader to be used by celery workers class CeleryLoader(CeleryBaseLoader): flask_app_factory = "abilian.sbe.app.create_app" celery = BaseCelery(loader=CeleryLoader) class Application(BaseApplication): APP_PLUGINS = BaseApplication.APP_PLUGINS + ( "abilian.sbe.apps.main", "abilian.sbe.apps.notifications", "abilian.sbe.apps.preferences", "abilian.sbe.apps.wiki", "abilian.sbe.apps.wall", "abilian.sbe.apps.documents", "abilian.sbe.apps.forum", # "abilian.sbe.apps.calendar", "abilian.sbe.apps.communities", "abilian.sbe.apps.social", "abilian.sbe.apps.preferences", ) script_manager = command_manager def __init__(self, name="abilian_sbe", config=None, **kwargs): BaseApplication.__init__(self, name, config=config, **kwargs) loader = jinja2.PackageLoader("abilian.sbe", "templates") self.register_jinja_loaders(loader) def init_extensions(self): BaseApplication.init_extensions(self) sbe.init_app(self) repository.init_app(self) converter.init_app(self) # SBE demo app bootstrap stuff _SBE_DEMO_SCRIPT = """\ #!{BIN_DIR}/python from __future__ import absolute_import import sys from abilian.sbe.app import command_entry_point sys.exit(command_entry_point()) """ _BASE_SERVER_ACTIVATE = BaseWSGIServer.server_activate def _on_http_server_activate(self, *args, **kwargs): """This function is used as to monkey patch BaseWSGIServer.server_activate during `setup_sbe_demo`.""" _BASE_SERVER_ACTIVATE(self) # now we are listening to socket host, port = self.server_address if host == "0.0.0.0": # chrome is not ok with 0.0.0.0 host = "localhost" url = "http://{host}:{port}/setup".format(host=host, port=port) if sys.platform == "win32": os.startfile(url) else: opener = "open" if sys.platform == "darwin" else "xdg-open" subprocess.call([opener, url]) # run with python -m abilian.sbe.app setup_sbe_app def setup_sbe_app(): """Basic set up SBE application. Must be run inside a virtualenv. Will create `abilian_sbe` script, run a local server and open browser on app's setup wizard. """ logger = logging.getLogger("sbe_demo") logger.setLevel(logging.INFO) if "VIRTUAL_ENV" not in os.environ: logger.error("Not in a virtualenv! Aborting.") return 1 bin_dir = Path(sys.prefix) / "bin" if not bin_dir.exists() or not bin_dir.is_dir(): logger.error("%s doesn't exists or is not a directory. Aborting", bin_dir) return 1 script_file = bin_dir / "abilian_sbe" if script_file.exists(): logger.info("%s already exists. Skipping creation.", script_file) else: with script_file.open("w") as out: logger.info('Create script: "%s".', script_file) content = _SBE_DEMO_SCRIPT.format(BIN_DIR=bin_dir) out.write(content) # 0755: -rwxr-xr-x script_file.chmod(0o755) current_app.config["PRODUCTION"] = True current_app.config["DEBUG"] = False current_app.config["ASSETS_DEBUG"] = False current_app.config["SITE_NAME"] = "Abilian SBE" current_app.config["MAIL_SENDER"] = "abilian-sbe-app@example.com" logger.info("Prepare CSS & JS files") command_manager.handle("abilian_sbe", ["assets", "build"]) # disabled init config: only if not running setupwizard # command_manager.handle('abilian_sbe', ['config', 'init']) # patch server used to launch browser here immediately after socket opened BaseWSGIServer.server_activate = _on_http_server_activate return command_manager.handle("abilian_sbe", ["run", "--hide-config"]) def command_entry_point(): command_manager.run(commands={"setup_sbe_app": Command(setup_sbe_app)}) if __name__ == "__main__": command_entry_point() PK ! e abilian/sbe/apps/__init__.py# coding=utf-8 PK ! {E E % abilian/sbe/apps/calendar/__init__.py# coding=utf-8 """Calendar module.""" from __future__ import absolute_import from abilian.sbe.extension import sbe def register_plugin(app): sbe.init_app(app) from .views import blueprint from .actions import register_actions blueprint.record_once(register_actions) app.register_blueprint(blueprint) PK ! , $ abilian/sbe/apps/calendar/actions.py# coding=utf-8 from __future__ import absolute_import, print_function, unicode_literals from abilian.i18n import _l from abilian.services import get_service from abilian.services.security import Admin from abilian.web.action import Action, FAIcon, actions from flask import g, url_for from flask_login import current_user class CalendarAction(Action): def url(self, context=None): return url_for("." + self.name, community_id=g.community.slug) class EventAction(CalendarAction): def pre_condition(self, context): event = context.get("object") return not not event def url(self, context=None): event = context.get("object") return url_for( "." + self.name, community_id=g.community.slug, event_id=event.id ) def is_admin(context): security = get_service("security") return security.has_role(current_user, Admin, object=context.get("object")) _actions = [ CalendarAction( "calendar:global", "new_event", _l("Create a new event"), icon="plus" ), CalendarAction("calendar:global", "index", _l("Upcoming events"), icon="list"), EventAction("calendar:event", "event", _l("View event"), icon=FAIcon("eye")), EventAction( "calendar:event", "event_edit", _l("Edit event"), icon=FAIcon("pencil") ), ] def register_actions(state): if not actions.installed(state.app): return with state.app.app_context(): actions.register(*_actions) PK ! (Ъ " abilian/sbe/apps/calendar/forms.py# coding=utf-8 """""" from __future__ import absolute_import, print_function, unicode_literals import bleach from abilian.i18n import _l from abilian.web.forms import Form from abilian.web.forms.fields import DateTimeField from abilian.web.forms.filters import strip from abilian.web.forms.validators import required from abilian.web.forms.widgets import RichTextWidget from wtforms import StringField, TextAreaField, ValidationError from wtforms.fields.html5 import URLField ALLOWED_TAGS = [ "a", "abbr", "acronym", "b", "blockquote", "br", "code", "em", "i", "li", "ol", "strong", "ul", "h1", "h2", "h3", "h4", "h5", "h6", "p", "u", "img", ] ALLOWED_ATTRIBUTES = { "*": ["title"], "p": ["style"], "a": ["href", "title"], "abbr": ["title"], "acronym": ["title"], "img": ["src", "alt", "title"], } ALLOWED_STYLES = ["text-align"] WIDGET_ALLOWED = {} for attr in ALLOWED_TAGS: allowed = ALLOWED_ATTRIBUTES.get(attr, True) if not isinstance(allowed, bool): allowed = {tag: True for tag in allowed} WIDGET_ALLOWED[attr] = allowed class EventForm(Form): title = StringField(label=_l("Title"), filters=(strip,), validators=[required()]) start = DateTimeField(_l("Start"), validators=[required()]) end = DateTimeField(_l("End"), validators=[required()]) location = TextAreaField(label=_l("Location"), filters=(strip,)) url = URLField(label=_l("URL"), filters=(strip,)) description = TextAreaField( label=_l("Description"), widget=RichTextWidget(allowed_tags=WIDGET_ALLOWED), filters=(strip,), validators=[required()], ) def validate_description(self, field): field.data = bleach.clean( field.data, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES, styles=ALLOWED_STYLES, strip=True, ) def validate_end(self, field): if self.start.data > self.end.data: raise ValidationError(_l("End date/time must be after start")) EventForm.start.kwargs["raw_data"] = [" | 09:00"] EventForm.end.kwargs["raw_data"] = [" | 18:00"] PK ! .Х # abilian/sbe/apps/calendar/models.py# coding=utf-8 """""" from __future__ import absolute_import, print_function, unicode_literals from abilian.core.entities import SEARCHABLE, Entity from sqlalchemy import Column, DateTime, Unicode from sqlalchemy.event import listens_for from sqlalchemy.orm import backref, relationship from abilian.sbe.apps.communities.models import Community, CommunityIdColumn, \ community_content @community_content class Event(Entity): __tablename__ = "sbe_event" community_id = CommunityIdColumn() #: The community this event belongs to community = relationship( Community, primaryjoin=(community_id == Community.id), backref=backref("events", cascade="all, delete-orphan"), ) title = Column(Unicode, nullable=False, default="", info=SEARCHABLE) description = Column(Unicode, nullable=False, default="", info=SEARCHABLE) location = Column(Unicode, nullable=False, default="", info=SEARCHABLE) start = Column(DateTime, nullable=False) end = Column(DateTime) url = Column(Unicode, nullable=False, default="") @listens_for(Event.title, "set", active_history=True) def _event_sync_name_title(entity, new_value, old_value, initiator): if entity.name != new_value: entity.name = new_value return new_value PK ! $ 7 abilian/sbe/apps/calendar/templates/calendar/_base.html{% extends "community/_base.html" %} {% from "macros/box.html" import m_box_menu %} {%- block sidebar %} {%- call m_box_menu() %}
{%- endcall %} {%- endblock %} PK ! _XxB B : abilian/sbe/apps/calendar/templates/calendar/archives.html{% extends "calendar/_base.html" %} {% from "macros/box.html" import m_box_menu, m_box_content %} {% block content %} {% call m_box_content(_("Past events")) %} {% for group in groups %}{{ _("No event has been posted to this calendar yet.") }}
{% endfor %} {% endcall %} {% endblock %} PK ! o 7 abilian/sbe/apps/calendar/templates/calendar/event.html{% extends "calendar/_base.html" %} {%- from "macros/box.html" import m_box_content, m_box_menu -%} {%- from "macros/form.html" import m_field -%} {%- block content %} {% call m_box_content() %} {# TODO #}{{ _("Start") }}: {{ event.start | datetimeformat }}
{{ _("End") }}: {{ event.end | datetimeformat }}
{% if event.location %}{{ event.location }}
{% endif %}{{ _("There are no upcoming events on this calendar yet.") }}
{% endfor %}[Archives]
{% endcall %} {% endblock %} PK ! e + abilian/sbe/apps/calendar/tests/__init__.py# coding=utf-8 PK ! Ɗ ( abilian/sbe/apps/calendar/tests/tests.py# coding=utf-8 """""" from __future__ import absolute_import, print_function, unicode_literals from datetime import datetime from flask import url_for from pytest import mark from ..models import Event def test_create_event(): start = datetime.now() event = Event(name="Test thread", start=start) assert event # TODO @mark.skip def test(community1, client, req_ctx): response = client.get(url_for("calendar.index", community_id=community1.slug)) assert response.status_code == 200 # @mark.skip # def test_event_indexed(community1, community2, db, client, req_ctx): # start = datetime.now() # event1 = Event(name="Test event", community=community1, start=start) # event2 = Event(name="Test other event", community=community2, start=start) # db.session.add(event1) # db.session.add(event2) # db.session.commit() # # svc = self.svc # obj_types = (Event.entity_type,) # with self.login(self.user_no_community): # res = svc.search("event", object_types=obj_types) # assert len(res) == 0 # # with self.login(self.user): # res = svc.search("event", object_types=obj_types) # assert len(res) == 1 # hit = res[0] # assert hit["object_key"] == event1.object_key # # with self.login(self.user_c2): # res = svc.search("event", object_types=obj_types) # assert len(res) == 1 # hit = res[0] # assert hit["object_key"] == event2.object_key PK ! I I " abilian/sbe/apps/calendar/views.py# coding=utf-8 """Forum views.""" from __future__ import absolute_import, print_function, unicode_literals from datetime import date, datetime from abilian.i18n import _l from abilian.web import url_for, views from abilian.web.action import ButtonAction from flask import g, render_template from toolz import groupby from ..communities.blueprint import Blueprint from ..communities.views import default_view_kw from .forms import EventForm from .models import Event blueprint = Blueprint( "calendar", __name__, url_prefix="/calendar", template_folder="templates" ) route = blueprint.route @route("/") def index(): events = Event.query.filter(Event.end > datetime.now()).order_by(Event.start).all() def get_month(event): year = event.start.year month = event.start.month return date(year, month, 1) groups = sorted(groupby(get_month, events).items()) ctx = {"groups": groups} return render_template("calendar/index.html", **ctx) @route("/archives/") def archives(): events = ( Event.query.filter(Event.end <= datetime.now()) .order_by(Event.start.desc()) .all() ) def get_month(event): year = event.start.year month = event.start.month return date(year, month, 1) groups = sorted(groupby(get_month, events).items(), reverse=True) ctx = {"groups": groups} return render_template("calendar/archives.html", **ctx) class BaseEventView(object): Model = Event Form = EventForm pk = "event_id" base_template = "community/_base.html" def index_url(self): return url_for(".index", community_id=g.community.slug) def view_url(self): return url_for(self.obj) class EventView(BaseEventView, views.ObjectView): methods = ["GET", "HEAD"] Form = EventForm template = "calendar/event.html" @property def template_kwargs(self): kw = super(EventView, self).template_kwargs kw["event"] = self.obj return kw event_view = EventView.as_view("event") views.default_view(blueprint, Event, "event_id", kw_func=default_view_kw)(event_view) route("/