Commit 663a9d49 authored by Robin Sonnabend's avatar Robin Sonnabend
Browse files

Improve tasks.py code quality

parent 32bfe777
......@@ -196,7 +196,7 @@ LATEX_BULLETPOINTS = [
# "logo": "asta-logo.tex", # optional: replaces the general template to include at the top of protocol.tex set by LATEX_LOGO_TEMPLATE
# "geometry": "bottom=1.6cm,top=1.6cm,inner=2.5cm,outer=1.0cm,footskip=1.0cm,headsep=0.6cm", # optional: replaces the general protocol page geometry set by LATEX_GEOMETRY
# "pagestyle": "fancy", # optional: replaces the general protocol pagestyle set by LATEX_PAGESTYLE
# "additionalpackages": ["[absolute]{textpos}", "{fancyheadings}"], # optional: replaces the general latex packages set by LATEX_ADDITIONAL_PACKAGES
# "additional_packages": ["[absolute]{textpos}", "{fancyheadings}"], # optional: replaces the general latex packages set by LATEX_ADDITIONAL_PACKAGES
# "headerfooter": True # optional: replaces the general LATEX_HEADER_FOOTER option
# }
#}
......
......@@ -9,15 +9,21 @@ import traceback
from copy import copy
import xmlrpc.client
from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP, MeetingReminder, TodoMail, DecisionDocument, TodoState, OldTodo, DecisionCategory
from models.database import (
Document, Protocol, Todo, Decision, TOP, MeetingReminder,
TodoMail, DecisionDocument, TodoState, OldTodo, DecisionCategory)
from models.errors import DateNotMatchingException
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, KNOWN_KEYS, WikiType
from utils import mail_manager, encode_kwargs, decode_kwargs, add_line_numbers, set_etherpad_text, get_etherpad_text, footnote_hash, parse_datetime_from_string
from protoparser import parse, ParserException, Element, Content, Text, Tag, Remark, Fork, RenderType
from shared import (
db, escape_tex, unhyphen, date_filter, datetime_filter, date_filter_long,
date_filter_short, time_filter, class_filter, KNOWN_KEYS, WikiType)
from utils import (
mail_manager, add_line_numbers,
set_etherpad_text, parse_datetime_from_string)
from protoparser import parse, ParserException, Tag, Remark, Fork, RenderType
from wiki import WikiClient, WikiException
from calendarpush import Client as CalendarClient, CalendarException
from legacy import lookup_todo_id, import_old_todos
from legacy import lookup_todo_id
import config
......@@ -32,7 +38,6 @@ texenv.filters["escape_tex"] = escape_tex
texenv.filters["unhyphen"] = unhyphen
texenv.trim_blocks = True
texenv.lstrip_blocks = True
#texenv.filters["url_complete"] = url_manager.complete
texenv.filters["datify"] = date_filter
texenv.filters["datify_long"] = date_filter_long
texenv.filters["datify_short"] = date_filter_short
......@@ -42,7 +47,9 @@ texenv.filters["class"] = class_filter
logo_template = getattr(config, "LATEX_LOGO_TEMPLATE", None)
if logo_template is not None:
texenv.globals["logo_template"] = logo_template
latex_geometry = getattr(config, "LATEX_GEOMETRY", "vmargin=1.5cm,hmargin={1.5cm,1.2cm},bindingoffset=8mm")
latex_geometry = getattr(
config, "LATEX_GEOMETRY",
"vmargin=1.5cm,hmargin={1.5cm,1.2cm},bindingoffset=8mm")
texenv.globals["latex_geometry"] = latex_geometry
raw_additional_packages = getattr(config, "LATEX_ADDITIONAL_PACKAGES", None)
additional_packages = []
......@@ -59,51 +66,67 @@ latex_header_footer = getattr(config, "LATEX_HEADER_FOOTER", False)
texenv.globals["latex_header_footer"] = latex_header_footer
latex_templates = getattr(config, "LATEX_TEMPLATES", None)
def provide_latex_template(template, documenttype):
_latex_template_documenttype_filename = {
"class": "protokoll2.cls",
"protocol": "protocol.tex",
"decision": "decision.tex"
}
_latex_template_filename = _latex_template_documenttype_filename[documenttype]
_latex_template_foldername = ""
if logo_template is not None:
texenv.globals["logo_template"] = logo_template
texenv.globals["latex_geometry"] = latex_geometry
texenv.globals["additional_packages"] = additional_packages
if latex_pagestyle is not None and latex_pagestyle:
texenv.globals["latex_pagestyle"] = latex_pagestyle
elif "latex_pagestyle" in texenv.globals:
del texenv.globals["latex_pagestyle"]
texenv.globals["latex_header_footer"] = latex_header_footer
if (latex_templates is not None) and (template is not ""):
if template in latex_templates:
if "provides" in latex_templates[template]:
_latex_template_foldername = (template + "/") if documenttype in latex_templates[template]["provides"] else ""
if "logo" in latex_templates[template]:
texenv.globals["logo_template"] = template + "/" + latex_templates[template]["logo"]
if "geometry" in latex_templates[template]:
texenv.globals["latex_geometry"] = latex_templates[template]["geometry"]
if "pagestyle" in latex_templates[template]:
if latex_templates[template]["pagestyle"]:
texenv.globals["latex_pagestyle"] = latex_templates[template]["pagestyle"]
if "additionalpackages" in latex_templates[template]:
_raw_additional_packages = latex_templates[template]["additionalpackages"]
_additional_packages = []
if _raw_additional_packages is not None:
for _package in _raw_additional_packages:
if "{" not in _package:
_package = "{{{}}}".format(_package)
_additional_packages.append(_package)
texenv.globals["additional_packages"] = _additional_packages
if "headerfooter" in latex_templates[template]:
texenv.globals["latex_header_footer"] = latex_templates[template]["headerfooter"]
return _latex_template_foldername + _latex_template_filename
_DOCUMENTTYPE_FILENAME_MAP = {
"class": "protokoll2.cls",
"protocol": "protocol.tex",
"decision": "decision.tex"
}
_PROVIDES = "provides"
_LOGO_TEMPLATE = "logo_template"
_LOGO = "logo"
_LATEX_GEOMETRY = "latex_geometry"
_GEOMETRY = "geometry"
_ADDITIONAL_PACKAGES = "additional_packages"
_LATEX_PAGESTYLE = "latex_pagestyle"
_PAGESTYLE = "pagestyle"
_LATEX_HEADER_FOOTER = "latex_header_footer"
_HEADER_FOOTER = "headerfooter"
_latex_template_filename = _DOCUMENTTYPE_FILENAME_MAP[documenttype]
_latex_template_foldername = ""
if logo_template is not None:
texenv.globals[_LOGO_TEMPLATE] = logo_template
texenv.globals[_LATEX_GEOMETRY] = latex_geometry
texenv.globals[_ADDITIONAL_PACKAGES] = additional_packages
if latex_pagestyle:
texenv.globals[_LATEX_PAGESTYLE] = latex_pagestyle
elif _LATEX_PAGESTYLE in texenv.globals:
del texenv.globals[_LATEX_PAGESTYLE]
texenv.globals[_LATEX_HEADER_FOOTER] = latex_header_footer
if latex_templates is not None and template != "":
if template in latex_templates:
template_data = latex_templates[template]
if _PROVIDES in template_data:
if documenttype in template_data[_PROVIDES]:
_latex_template_foldername = template
if _LOGO in template_data:
texenv.globals[_LOGO_TEMPLATE] = os.path.join(
template, template_data[_LOGO])
if _GEOMETRY in template_data:
texenv.globals[_LATEX_GEOMETRY] = template_data[_GEOMETRY]
if _PAGESTYLE in template_data:
if template_data[_PAGESTYLE]:
texenv.globals[_LATEX_PAGESTYLE] = (
template_data[_PAGESTYLE])
if _ADDITIONAL_PACKAGES in template_data:
_raw_additional_packages = template_data[_ADDITIONAL_PACKAGES]
_additional_packages = []
if _raw_additional_packages is not None:
for _package in _raw_additional_packages:
if "{" not in _package:
_package = "{{{}}}".format(_package)
_additional_packages.append(_package)
texenv.globals[_ADDITIONAL_PACKAGES] = _additional_packages
if _HEADER_FOOTER in latex_templates[template]:
texenv.globals[_LATEX_HEADER_FOOTER] = (
template_data[_HEADER_FOOTER])
return os.path.join(_latex_template_foldername, _latex_template_filename)
mailenv = app.create_jinja_environment()
mailenv.trim_blocks = True
mailenv.lstrip_blocks = True
#mailenv.filters["url_complete"] = url_manager.complete
mailenv.filters["datify"] = date_filter
mailenv.filters["datetimify"] = datetime_filter
......@@ -124,43 +147,46 @@ wikienv.filters["timify"] = time_filter
wikienv.filters["class"] = class_filter
def _make_error(protocol, *args):
error = protocol.create_error(*args)
db.session.add(error)
db.session.commit()
ID_FIELD_BEGINNING = "id "
def parse_protocol(protocol, **kwargs):
parse_protocol_async.delay(protocol.id, encode_kwargs(kwargs))
def parse_protocol(protocol):
parse_protocol_async.delay(protocol.id)
@celery.task
def parse_protocol_async(protocol_id, encoded_kwargs):
def parse_protocol_async(protocol_id):
with app.app_context():
with app.test_request_context("/"):
try:
kwargs = decode_kwargs(encoded_kwargs)
protocol = Protocol.query.filter_by(id=protocol_id).first()
protocol = Protocol.first_by_id(protocol_id)
if protocol is None:
raise Exception("No protocol given. Aborting parsing.")
parse_protocol_async_inner(protocol, encoded_kwargs)
parse_protocol_async_inner(protocol)
except Exception as exc:
stacktrace = traceback.format_exc()
error = protocol.create_error("Parsing", "Exception",
return _make_error(
protocol, "Parsing", "Exception",
"{}\n\n{}".format(str(exc), stacktrace))
db.session.add(error)
db.session.commit()
def parse_protocol_async_inner(protocol, encoded_kwargs):
def parse_protocol_async_inner(protocol):
old_errors = list(protocol.errors)
for error in old_errors:
protocol.errors.remove(error)
db.session.commit()
if protocol.source is None or len(protocol.source.strip()) == 0:
error = protocol.create_error("Parsing", "Protocol source is empty", "")
db.session.add(error)
db.session.commit()
return
return _make_error(protocol, "Parsing", "Protocol source is empty", "")
if protocol.source == config.EMPTY_ETHERPAD:
error = protocol.create_error("Parsing", "The etherpad is unmodified and does not contain a protocol.", protocol.source)
db.session.add(error)
db.session.commit()
return
return _make_error(
protocol, "Parsing", "The etherpad is unmodified and does not "
"contain a protocol.", protocol.source)
tree = None
try:
tree = parse(protocol.source)
......@@ -169,59 +195,64 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
if exc.linenumber is not None:
source_lines = protocol.source.splitlines()
start_index = max(0, exc.linenumber - config.ERROR_CONTEXT_LINES)
end_index = min(len(source_lines) - 1, exc.linenumber + config.ERROR_CONTEXT_LINES)
end_index = min(
len(source_lines) - 1,
exc.linenumber + config.ERROR_CONTEXT_LINES)
context = "\n".join(source_lines[start_index:end_index])
if exc.tree is not None:
context += "\n\nParsed syntax tree was:\n" + str(exc.tree.dump())
error = protocol.create_error("Parsing", str(exc), context)
db.session.add(error)
db.session.commit()
return
remarks = {element.name: element for element in tree.children if isinstance(element, Remark)}
return _make_error(protocol, "Parsing", str(exc), context)
remarks = {
element.name: element
for element in tree.children
if isinstance(element, Remark)
}
required_fields = copy(KNOWN_KEYS)
for default_meta in protocol.protocoltype.metas:
required_fields.append(default_meta.key)
if not config.PARSER_LAZY:
missing_fields = [field for field in required_fields if field not in remarks]
missing_fields = [
field
for field in required_fields
if field not in remarks
]
if len(missing_fields) > 0:
error = protocol.create_error("Parsing", "Du hast vergessen, Metadaten anzugeben.", ", ".join(missing_fields))
db.session.add(error)
db.session.commit()
return
return _make_error(
protocol, "Parsing", "Du hast vergessen, Metadaten anzugeben.",
", ".join(missing_fields))
try:
protocol.fill_from_remarks(remarks)
except ValueError:
error = protocol.create_error(
"Parsing", "Invalid fields",
return _make_error(
protocol, "Parsing", "Invalid fields",
"Date or time fields are not '%d.%m.%Y' respectively '%H:%M', "
"but rather {}".format(
", ".join([
remarks["Datum"].value.strip(),
remarks["Beginn"].value.strip(),
remarks["Ende"].value.strip()
])))
db.session.add(error)
db.session.commit()
return
", ".join([
remarks["Datum"].value.strip(),
remarks["Beginn"].value.strip(),
remarks["Ende"].value.strip()
])))
except DateNotMatchingException as exc:
error = protocol.create_error("Parsing", "Date not matching",
"This protocol's date should be {}, but the protocol source says {}.".format(date_filter(exc.original_date) if exc.original_date is not None else "not present", date_filter(exc.protocol_date) if exc.protocol_date is not None else "not present"))
db.session.add(error)
db.session.commit()
return
# tags
return _make_error(
protocol, "Parsing", "Date not matching",
"This protocol's date should be {}, but the protocol source "
"says {}.".format(
date_filter(exc.original_date)
if exc.original_date is not None
else "not present",
date_filter(exc.protocol_date)
if exc.protocol_date is not None
else "not present"))
# tags
tags = tree.get_tags()
elements = tree.get_visible_elements(show_private=True)
public_elements = tree.get_visible_elements(show_private=False)
for tag in tags:
if tag.name not in Tag.KNOWN_TAGS:
error = protocol.create_error("Parsing", "Invalid tag",
return _make_error(
protocol, "Parsing", "Invalid tag",
"The tag in line {} has the kind '{}', which is "
"not defined. This is probably an error mit a missing "
"semicolon.".format(tag.linenumber, tag.name))
db.session.add(error)
db.session.commit()
return
# todos
old_todo_number_map = {}
for todo in protocol.todos:
......@@ -233,14 +264,12 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
raw_todos = []
for todo_tag in todo_tags:
if len(todo_tag.values) < 2:
error = protocol.create_error("Parsing", "Invalid todo-tag",
return _make_error(
protocol, "Parsing", "Invalid todo-tag",
"The todo tag in line {} needs at least "
"information on who and what, "
"but has less than that. This is probably "
"a missing semicolon.".format(todo_tag.linenumber))
db.session.add(error)
db.session.commit()
return
who = todo_tag.values[0]
what = todo_tag.values[1]
field_id = None
......@@ -254,39 +283,45 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
try:
field_id = int(other_field[len(ID_FIELD_BEGINNING):])
except ValueError:
error = protocol.create_error("Parsing", "Non-numerical todo ID",
"The todo in line {} has a nonnumerical ID, but needs "
"something like \"id 1234\"".format(todo_tag.linenumber))
db.session.add(error)
db.session.commit()
return
return _make_error(
protocol, "Parsing", "Non-numerical todo ID",
"The todo in line {} has a nonnumerical ID, but needs "
"something like \"id 1234\"".format(
todo_tag.linenumber))
else:
try:
field_state = TodoState.from_name(other_field)
continue
except ValueError:
pass
try:
field_date = datetime.strptime(other_field, "%d.%m.%Y")
continue
except ValueError:
try:
field_date = datetime.strptime(other_field, "%d.%m.%Y")
except ValueError:
try:
field_state, field_date = TodoState.from_name_with_date(other_field.strip(), protocol=protocol)
except ValueError:
try:
field_state = TodoState.from_name_lazy(other_field)
except ValueError:
error = protocol.create_error("Parsing",
"Invalid field",
"The todo in line {} has the field '{}', but "
"this does neither match a date (\"%d.%m.%Y\") "
"nor a state.".format(
todo_tag.linenumber, other_field))
db.session.add(error)
db.session.commit()
return
raw_todos.append((who, what, field_id, field_state, field_date, todo_tag))
pass
try:
field_state, field_date = TodoState.from_name_with_date(
other_field.strip(), protocol=protocol)
continue
except ValueError:
pass
try:
field_state = TodoState.from_name_lazy(other_field)
except ValueError:
return _make_error(
protocol, "Parsing", "Invalid field",
"The todo in line {} has the field '{}', but "
"this does neither match a date (\"%d.%m.%Y\") "
"nor a state.".format(
todo_tag.linenumber, other_field))
raw_todos.append(
(who, what, field_id, field_state, field_date, todo_tag))
for (_, _, field_id, _, _, _) in raw_todos:
if field_id is not None:
old_todos = [todo for todo in old_todos
if todo.id != field_id]
old_todos = [
todo for todo in old_todos
if todo.id != field_id
]
for todo in old_todos:
protocol.todos.remove(todo)
db.session.commit()
......@@ -294,28 +329,23 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
if field_state is None:
field_state = TodoState.open
if field_state.needs_date() and field_date is None:
error = protocol.create_error("Parsing",
"Todo missing date",
return _make_error(
protocol, "Parsing", "Todo missing date",
"The todo in line {} has a state that needs a date, "
"but the todo does not have one.".format(todo_tag.linenumber))
db.session.add(error)
db.session.commit()
return
who = who.strip()
what = what.strip()
todo = None
if field_id is not None:
todo = Todo.query.filter_by(number=field_id).first()
if todo is None and not config.PARSER_LAZY:
error = protocol.create_error("Parsing",
"Invalid Todo ID",
"The todo in line {} has the ID {}, but there is no "
"Todo with that ID.".format(todo_tag.linenumber, field_id))
db.session.add(error)
db.session.commit()
return
return _make_error(
protocol, "Parsing", "Invalid Todo ID",
"The todo in line {} has the ID {}, but there is no "
"Todo with that ID.".format(todo_tag.linenumber, field_id))
if todo is None and field_id is None and what in old_todo_number_map:
todo = Todo(protocoltype_id=protocol.protocoltype.id,
todo = Todo(
protocoltype_id=protocol.protocoltype.id,
who=who, description=what, state=field_state,
date=field_date, number=old_todo_number_map[what])
db.session.add(todo)
......@@ -326,7 +356,8 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
OldTodo.protocol_key == protocol_key).all()
if len(old_candidates) == 0:
# new protocol
todo = Todo(protocoltype_id=protocol.protocoltype.id,
todo = Todo(
protocoltype_id=protocol.protocoltype.id,
who=who, description=what, state=field_state,
date=field_date)
db.session.add(todo)
......@@ -338,7 +369,8 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
number = field_id or lookup_todo_id(old_candidates, who, what)
todo = Todo.query.filter_by(number=number).first()
if todo is None:
todo = Todo(protocoltype_id=protocol.protocoltype.id,
todo = Todo(
protocoltype_id=protocol.protocoltype.id,
who=who, description=what, state=field_state,
date=field_date, number=number)
db.session.add(todo)
......@@ -360,13 +392,12 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
decision_tags = [tag for tag in tags if tag.name == "beschluss"]
for decision_tag in decision_tags:
if decision_tag not in public_elements:
error = protocol.create_error("Parsing", "Decision in private context.",
"The decision in line {} is in a private context, but decisions are "
"and have to be public. Please move it to a public spot.".format(
decision_tag.linenumber))
db.session.add(error)
db.session.commit()
return
return _make_error(
protocol, "Parsing", "Decision in private context.",
"The decision in line {} is in a private context, but "
"decisions are and have to be public. "
"Please move it to a public spot.".format(
decision_tag.linenumber))
old_decisions = list(protocol.decisions)
for decision in old_decisions:
protocol.decisions.remove(decision)
......@@ -374,36 +405,35 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
decisions_to_render = []
for decision_tag in decision_tags:
if len(decision_tag.values) == 0:
error = protocol.create_error("Parsing", "Empty decision found.",
"The decision in line {} is empty.".format(decision_tag.linenumber))
db.session.add(error)
db.session.commit()
return
return _make_error(
protocol, "Parsing", "Empty decision found.",
"The decision in line {} is empty.".format(
decision_tag.linenumber))
decision_content = decision_tag.values[0]
decision_categories = []
for decision_category_name in decision_tag.values[1:]:
decision_category = DecisionCategory.query.filter_by(protocoltype_id=protocol.protocoltype.id, name=decision_category_name).first()
decision_category = DecisionCategory.query.filter_by(
protocoltype_id=protocol.protocoltype.id,
name=decision_category_name).first()
if decision_category is None:
category_candidates = DecisionCategory.query.filter_by(protocoltype_id=protocol.protocoltype.id).all()
category_candidates = DecisionCategory.query.filter_by(
protocoltype_id=protocol.protocoltype.id).all()
category_names = [
"'{}'".format(category.name)
for category in category_candidates
]
error = protocol.create_error("Parsing",
"Unknown decision category",
return _make_error(
protocol, "Parsing", "Unknown decision category",
"The decision in line {} has the category {}, "
"but there is no such category. "
"Known categories are {}".format(
decision_tag.linenumber,
decision_category_name,
", ".join(category_names)))
db.session.add(error)
db.session.commit()
return
else:
decision_categories.append(decision_category)
decision = Decision(protocol_id=protocol.id,
content=decision_content)
decision = Decision(
protocol_id=protocol.id, content=decision_content)
db.session.add(decision)
db.session.commit()
for decision_category in decision_categories:
......@@ -412,66 +442,67 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
decisions_to_render.append((decision, decision_tag))
for decision, decision_tag in decisions_to_render:
decision_top = decision_tag.fork.get_top()
decision_content = texenv.get_template(provide_latex_template(protocol.protocoltype.latex_template, "decision")).render(
render_type=RenderType.latex, decision=decision,
protocol=protocol, top=decision_top, show_private=True)
decision_content = texenv.get_template(provide_latex_template(
protocol.protocoltype.latex_template, "decision")).render(
render_type=RenderType.latex, decision=decision,
protocol=protocol, top=decision_top, show_private=True)
maxdepth = decision_top.get_maxdepth()
compile_decision(decision_content, decision, maxdepth=maxdepth)
# Footnotes
footnote_tags = [tag for tag in tags if tag.name == "footnote"]
public_footnote_tags = [tag for tag in footnote_tags if tag in public_elements]
footnote_tags = [
tag for tag in tags
if tag.name == "footnote"
]
public_footnote_tags = [
tag for tag in footnote_tags
if tag in public_elements
]
# new Protocols
protocol_tags = [tag for tag in tags if tag.name == "sitzung"]
for protocol_tag in protocol_tags:
if len(protocol_tag.values) not in {1, 2}:
error = protocol.create_error("Parsing",
"Falsche Verwendung von [sitzung;…].",
return _make_error(
protocol, "Parsing", "Falsche Verwendung von [sitzung;…].",
"Der Tag \"sitzung\" benötigt immer ein Datum "
"und optional eine Uhrzeit, also ein bis zwei Argumente. "
"Stattdessen wurden {} übergeben, nämlich {}".format(
len(protocol_tag.values),
protocol_tag.values))
db.session.add(error)
db.ession.commit()
return
len(protocol_tag.values),
protocol_tag.values))
else:
try:
protocol_date = parse_datetime_from_string(
protocol_tag.values[0])
parse_datetime_from_string(protocol_tag.values[0])
except ValueError as exc:
error = protocol.create_error("Parsing", "Invalides Datum",
return _make_error(
protocol, "Parsing", "Invalides Datum",
"'{}' ist kein valides Datum.".format(
protocol_tag.values[0]))
db.session.add(error)
db.session.commit()
return
if len(protocol_tag.values) > 1:
try:
protocol_time = datetime.strptime(protocol_tag.values[1], "%H:%M")
datetime.strptime(protocol_tag.values[1], "%H:%M")
except ValueError:
error = protocol.create_error("Parsing", "Invalide Uhrzeit",
return _make_error(
protocol, "Parsing", "Invalide Uhrzeit",
"'{}' ist keine valide Uhrzeit.".format(
protocol_tag.values[1]))
db.session.add(error)
db.session.commit()
return
for protocol_tag in protocol_tags:
new_protocol_date = parse_datetime_from_string(protocol_tag.values[0])