Commit b3f71d25 authored by Robin Sonnabend's avatar Robin Sonnabend

Pushing to Wiki

parent a84e567c
SQLALCHEMY_DATABASE_URI = "postgresql://proto3:@/proto3"
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_DATABASE_URI = "postgresql://proto3:@/proto3" # change
SQLALCHEMY_TRACK_MODIFICATIONS = False # do not change
SECRET_KEY = "abc"
SECRET_KEY = "something random"
DEBUG = False
......@@ -13,16 +13,44 @@ MAIL_PASSWORD = "password"
MAIL_PREFIX = "protokolle"
CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_TASK_SERIALIZER = "pickle"
CELERY_ACCEPT_CONTENT = ["pickle"]
CELERY_TASK_SERIALIZER = "pickle" # do not change
CELERY_ACCEPT_CONTENT = ["pickle"] # do not change
URL_ROOT = "protokolle.example.com"
URL_PROTO = "https"
URL_PATH = "/"
URL_PARAMS = ""
LDAP_PROVIDER_URL = "ldaps://auth.example.com:389"
LDAP_BASE = "dc=example,dc=example,dc=com"
LDAP_PROTOCOL_VERSION = 3 # do not change
ETHERPAD_URL = "https://fachschaften.rwth-aachen.de/etherpad"
EMPTY_ETHERPAD = """Welcome to Etherpad!
This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!
Get involved with Etherpad at http://etherpad.org
""" # do not change
WIKI_ACTIVE = True
WIKI_API_URL = "https://wiki.example.com/wiki/api.php"
WIKI_ANONYMOUS = False
WIKI_USER = "user"
WIKI_PASSWORD = "password"
WIKI_DOMAIN = "domain" # set to None if not necessary
SESSION_PROTECTION = "strong"
SECURITY_KEY = "some other random string"
ERROR_CONTEXT_LINES = 3
PAGE_LENGTH = 20
PAGE_DIFF = 3
# choose something nice from fc-list
FONTS = {
"main": {
......@@ -50,3 +78,8 @@ FONTS = {
"bolditalic": "Nimbus Mono PS"
}
}
DOCUMENTS_PATH = "documents"
PRIVATE_KEYWORDS = ["private", "internal", "privat", "intern"]
"""empty message
Revision ID: d8c0c74b88bd
Revises: 495509e8f49a
Create Date: 2017-02-25 20:16:05.371638
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd8c0c74b88bd'
down_revision = '495509e8f49a'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('protocoltypes', sa.Column('use_wiki', sa.Boolean(), nullable=True))
op.add_column('protocoltypes', sa.Column('wiki_category', sa.String(), nullable=True))
op.add_column('protocoltypes', sa.Column('wiki_only_public', sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('protocoltypes', 'wiki_only_public')
op.drop_column('protocoltypes', 'wiki_category')
op.drop_column('protocoltypes', 'use_wiki')
# ### end Alembic commands ###
......@@ -26,6 +26,9 @@ class ProtocolType(db.Model):
public_group = db.Column(db.String)
private_mail = db.Column(db.String)
public_mail = db.Column(db.String)
use_wiki = db.Column(db.Boolean)
wiki_category = db.Column(db.String)
wiki_only_public = db.Column(db.Boolean)
protocols = relationship("Protocol", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="Protocol.id")
default_tops = relationship("DefaultTOP", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="DefaultTOP.number")
......@@ -33,7 +36,8 @@ class ProtocolType(db.Model):
todos = relationship("Todo", backref=backref("protocoltype"), order_by="Todo.id")
def __init__(self, name, short_name, organization,
is_public, private_group, public_group, private_mail, public_mail):
is_public, private_group, public_group, private_mail, public_mail,
use_wiki, wiki_category, wiki_only_public):
self.name = name
self.short_name = short_name
self.organization = organization
......@@ -42,10 +46,19 @@ class ProtocolType(db.Model):
self.public_group = public_group
self.private_mail = private_mail
self.public_mail = public_mail
self.use_wiki = use_wiki
self.wiki_category = wiki_category
self.wiki_only_public = wiki_only_public
def __repr__(self):
return "<ProtocolType(id={}, short_name={}, name={}, organization={}, is_public={}, private_group={}, public_group={})>".format(
self.id, self.short_name, self.name, self.organization, self.is_public, self.private_group, self.public_group)
return ("<ProtocolType(id={}, short_name={}, name={}, "
"organization={}, is_public={}, private_group={}, "
"public_group={}, use_wiki={}, wiki_category='{}', "
"wiki_only_public={})>".format(
self.id, self.short_name, self.name,
self.organization, self.is_public, self.private_group,
self.public_group, self.use_wiki, self.wiki_category,
self.wiki_only_public))
def get_latest_protocol(self):
candidates = sorted([protocol for protocol in self.protocols if protocol.is_done()], key=lambda p: p.date, reverse=True)
......@@ -153,6 +166,9 @@ class Protocol(db.Model):
self.protocoltype.short_name.lower(),
self.date.strftime("%y-%m-%d"))
def get_wiki_title(self):
return "Protokoll:{}-{:%Y-%m-%d}".format(self.protocoltype.short_name, self.date)
def get_etherpad_link(self):
identifier = self.get_identifier()
if identifier is None:
......@@ -305,8 +321,15 @@ class Todo(db.Model):
def get_state(self):
return "[Erledigt]" if self.done else "[Offen]"
def get_state_tex(self):
def get_state_plain(self):
return "Erledigt" if self.done else "Aktiv"
def get_state_tex(self):
return self.get_state_plain()
def is_new(self, current_protocol=None):
if current_protocol is not None:
return self.get_first_protocol() == current_protocol
return len(self.protocols) == 1
def render_html(self):
parts = [
......@@ -317,16 +340,21 @@ class Todo(db.Model):
return " ".join(parts)
def render_latex(self, current_protocol=None):
is_new = len(self.protocols) == 1
if current_protocol is not None:
is_new = self.get_first_protocol() == current_protocol
return r"\textbf{{{}}}: {}: {} -- {}".format(
"Neuer Todo" if is_new else "Todo",
"Neuer Todo" if self.is_new(current_protocol) else "Todo",
self.who,
self.description,
self.get_state_tex()
)
def render_wikitext(self, current_protocol=None):
return "'''{}:''' {}: {} - {}".format(
"Neuer Todo" if self.is_new(current_protocol) else "Todo",
self.who,
self.description,
self.get_state_plain()
)
class TodoProtocolAssociation(db.Model):
......
......@@ -197,6 +197,12 @@ class Tag:
return r"\textbf{{{}:}} {}".format(escape_tex(self.name.capitalize()), escape_tex(self.values[0]))
elif render_type == RenderType.plaintext:
return "{}: {}".format(self.name.capitalize(), self.values[0])
elif render_type == RenderType.wikitext:
if self.name == "url":
return "[{0} {0}]".format(self.values[0])
elif self.name == "todo":
return self.todo.render_wikitext(current_protocol=protocol)
return "'''{}:''' {}".format(self.name.capitalize(), self.values[0])
else:
raise _not_implemented(self, render_type)
......@@ -291,7 +297,7 @@ class Fork(Element):
def test_private(self, name):
stripped_name = name.replace(":", "").strip()
return stripped_name in config.PRIVATE_KEYS
return stripped_name in config.PRIVATE_KEYWORDS
def render(self, render_type, show_private, level, protocol=None):
name_line = self.name if self.name is not None and len(self.name) > 0 else ""
......@@ -317,14 +323,14 @@ class Fork(Element):
else:
return "\n".join([name_line, begin_line, content_lines, end_line])
elif render_type == RenderType.wikitext:
title_line = "{0}{1}{0}".format("=" * (level + 2), name_line)
title_line = "{0} {1} {0}".format("=" * (level + 2), name_line)
content_parts = []
for child in self.children:
part = child.render(render_type, show_private, level=level+1, protocol=protocol)
if len(part.strip()) == 0:
continue
content_parts.append(part)
content_lines = "{}\n{}".format(title_line, "\n".join(content_parts))
content_lines = "{}\n\n{}\n".format(title_line, "\n\n".join(content_parts))
if self.test_private(self.name) and not show_private:
return ""
else:
......
......@@ -88,7 +88,9 @@ def new_type():
protocoltype = ProtocolType(form.name.data, form.short_name.data,
form.organization.data, form.is_public.data,
form.private_group.data, form.public_group.data,
form.private_mail.data, form.public_mail.data)
form.private_mail.data, form.public_mail.data,
form.use_wiki.data, form.wiki_category.data,
form.wiki_only_public.data)
db.session.add(protocoltype)
db.session.commit()
flash("Der Protokolltyp {} wurde angelegt.".format(protocoltype.name), "alert-success")
......
......@@ -11,6 +11,7 @@ from server import celery, app
from shared import db, escape_tex, unhyphen, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, class_filter
from utils import mail_manager, url_manager, encode_kwargs, decode_kwargs
from parser import parse, ParserException, Element, Content, Text, Tag, Remark, Fork, RenderType
from wiki import WikiClient, WikiException
import config
......@@ -206,10 +207,28 @@ def parse_protocol_async(protocol_id, encoded_kwargs):
for show_private in privacy_states:
latex_source = texenv.get_template("protocol.tex").render(render_type=RenderType.latex, show_private=show_private, **render_kwargs)
compile(latex_source, protocol, show_private=show_private)
# TODO render and push wiki
if protocol.protocoltype.use_wiki:
wiki_source = render_template("protocol.wiki", render_type=RenderType.wikitext, show_private=not protocol.protocoltype.wiki_only_public, **render_kwargs).replace("\n\n\n", "\n\n")
push_to_wiki(protocol, wiki_source, "Automatisch generiert vom Protokollsystem 3.0")
protocol.done = True
db.session.commit()
def push_to_wiki(protocol, content, summary):
push_to_wiki_async.delay(protocol.id, content, summary)
@celery.task
def push_to_wiki_async(protocol_id, content, summary):
with WikiClient() as wiki_client, app.app_context():
protocol = Protocol.query.filter_by(id=protocol_id).first()
try:
wiki_client.edit_page(
title=protocol.get_wiki_title(),
content=content,
summary="Automatisch generiert vom Protokollsystem 3.")
except WikiException as exc:
error = protocol.create_error("Pushing to Wiki", "Pushing to Wiki failed.", str(exc))
def compile(content, protocol, show_private):
compile_async.delay(content, protocol.id, show_private)
......
{{'{{'}}Infobox Protokoll
| name = {{protocol.protocoltype.name}}
| datum = {{protocol.date|datify}}
| zeit = von {{protocol.start_time|timify}} bis {{protocol.end_time|timify}}
| protokollant = {{protocol.author}}
| anwesende = {{protocol.participants}}
{{'}}'}}
== Beschlüsse ==
{% if protocol.decisions|length > 0 %}
{% for decision in protocol.decisions %}
* {{decision.content}}
{% endfor %}
{% else %}
* keine Beschlüsse
{% endif %}
{% for top in tree.children %}
{% if top|class == "Fork" %}
{{top.render(render_type=render_type, level=0, show_private=show_private, protocol=protocol)}}
{% endif %}
{% endfor %}
[[Kategorie:{{protocol.protocoltype.wiki_category}}]]
......@@ -15,6 +15,9 @@ class ProtocolTypeForm(FlaskForm):
public_group = StringField("Öffentliche Gruppe")
private_mail = StringField("Interner Verteiler")
public_mail = StringField("Öffentlicher Verteiler")
wiki_category = StringField("Wiki-Kategorie")
use_wiki = BooleanField("Wiki benutzen")
wiki_only_public = BooleanField("Wiki ist öffentlich")
class DefaultTopForm(FlaskForm):
name = StringField("Name", validators=[InputRequired("Du musst einen Namen angeben.")])
......
......@@ -95,12 +95,16 @@ class ProtocolTypeTable(SingleValueTable):
super().__init__(protocoltype.name, protocoltype, newlink=url_for("edit_type", type_id=protocoltype.id))
def headers(self):
return ["Name", "Abkürzung", "Organisation", "Öffentlich",
headers = ["Name", "Abkürzung", "Organisation", "Öffentlich",
"Interne Gruppe", "Öffentliche Gruppe",
"Interner Verteiler", "Öffentlicher Verteiler"]
"Interner Verteiler", "Öffentlicher Verteiler",
"Wiki"]
if self.value.use_wiki:
headers.append("Wiki-Kategorie")
return headers
def row(self):
return [
row = [
self.value.name,
self.value.short_name,
self.value.organization,
......@@ -108,8 +112,12 @@ class ProtocolTypeTable(SingleValueTable):
self.value.private_group,
self.value.public_group,
self.value.private_mail,
self.value.public_mail
self.value.public_mail,
Table.bool(self.value.use_wiki) + (", " + ("Öffentlich" if self.value.wiki_only_public else "Intern")) if self.value.use_wiki else ""
]
if self.value.use_wiki:
row.append(self.value.wiki_category)
return row
class DefaultTOPsTable(Table):
def __init__(self, tops, protocoltype=None):
......
import requests
import json
import config
HTTP_STATUS_OK = 200
class WikiException(Exception):
pass
def _filter_params(params):
result = {}
for key, value in sorted(params.items(), key=lambda t: t[0] == "token"):
if isinstance(value, bool):
if value:
result[key] = ""
else:
result[key] = value
return result
class WikiClient:
def __init__(self, active=None, endpoint=None, anonymous=None, user=None, password=None, domain=None):
self.active = active if active is not None else config.WIKI_ACTIVE
self.endpoint = endpoint if endpoint is not None else config.WIKI_API_URL
self.anonymous = anonymous if anonymous is not None else config.WIKI_ANONYMOUS
self.user = user if user is not None else config.WIKI_USER
self.password = password if password is not None else config.WIKI_PASSWORD
self.domain = domain if domain is not None else config.WIKI_DOMAIN
self.token = None
self.cookies = requests.cookies.RequestsCookieJar()
def __enter__(self):
if not self.anonymous:
self.login()
return self
def __exit__(self, type, value, traceback):
if not self.anonymous:
self.logout()
def is_logged_in(self):
return self.token is not None
def login(self):
if not self.active:
return
# todo: Change this to the new MediaWiki tokens api once the wiki is updated
token_answer = self.do_action("login", method="post", lgname=self.user)
if "login" not in token_answer or "token" not in token_answer["login"]:
raise WikiException("No token in login answer.")
lgtoken = token_answer["login"]["token"]
login_answer = self.do_action("login", method="post", lgname=self.user, lgpassword=self.password, lgdomain=self.domain, lgtoken=lgtoken)
if ("login" not in login_answer
or "result" not in login_answer["login"]
or login_answer["login"]["result"] != "Success"):
raise WikiException("Login not successful.")
def logout(self):
if not self.active:
return
self.do_action("logout")
def edit_page(self, title, content, summary, recreate=True, createonly=False):
if not self.active:
return
# todo: port to new api once the wiki is updated
prop_answer = self.do_action("query", method="get", prop="info", intoken="edit", titles=title)
if ("query" not in prop_answer
or "pages" not in prop_answer["query"]):
raise WikiException("Can't get token for page {}".format(title))
pages = prop_answer["query"]["pages"]
edit_token = None
for page in pages.values():
if page["title"] == title:
edit_token = page["edittoken"]
break
if edit_token is None:
raise WikiException("Can't get token for page {}".format(title))
edit_answer = self.do_action(action="edit", method="post", data={"text": content},
token=edit_token, title=title,
summary=summary, recreate=recreate,
createonly=createonly, bot=True)
def do_action(self, action, method="get", data=None, **kwargs):
if not self.active:
return
kwargs["action"] = action
kwargs["format"] = "json"
params = _filter_params(kwargs)
req = None
if method == "get":
req = requests.get(self.endpoint, cookies=self.cookies, params=params)
elif method == "post":
req = requests.post(self.endpoint, cookies=self.cookies, data=data, params=params)
if req.status_code != HTTP_STATUS_OK:
raise WikiException("HTTP status code {} on action {}.".format(req.status_code, action))
self.cookies = req.cookies
return req.json()
def main():
with WikiClient() as client:
client.edit_page(title="Test", content="This is a very long text.", summary="API client test")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment