Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
protokollsystem
proto3
Commits
4f136efa
Commit
4f136efa
authored
Feb 26, 2017
by
Robin Sonnabend
Browse files
Invitation mails
parent
00ede38d
Changes
10
Hide whitespace changes
Inline
Side-by-side
migrations/versions/515d261a624b_.py
0 → 100644
View file @
4f136efa
"""empty message
Revision ID: 515d261a624b
Revises: 77bf71eef07f
Create Date: 2017-02-26 00:33:13.555804
"""
from
alembic
import
op
import
sqlalchemy
as
sa
# revision identifiers, used by Alembic.
revision
=
'515d261a624b'
down_revision
=
'77bf71eef07f'
branch_labels
=
None
depends_on
=
None
def
upgrade
():
# ### commands auto generated by Alembic - please adjust! ###
op
.
add_column
(
'protocoltypes'
,
sa
.
Column
(
'usual_time'
,
sa
.
Time
(),
nullable
=
True
))
# ### end Alembic commands ###
def
downgrade
():
# ### commands auto generated by Alembic - please adjust! ###
op
.
drop_column
(
'protocoltypes'
,
'usual_time'
)
# ### end Alembic commands ###
migrations/versions/77bf71eef07f_.py
0 → 100644
View file @
4f136efa
"""empty message
Revision ID: 77bf71eef07f
Revises: f91d760158dc
Create Date: 2017-02-26 00:26:48.499578
"""
from
alembic
import
op
import
sqlalchemy
as
sa
# revision identifiers, used by Alembic.
revision
=
'77bf71eef07f'
down_revision
=
'f91d760158dc'
branch_labels
=
None
depends_on
=
None
def
upgrade
():
# ### commands auto generated by Alembic - please adjust! ###
op
.
add_column
(
'meetingreminders'
,
sa
.
Column
(
'additional_text'
,
sa
.
String
(),
nullable
=
True
))
# ### end Alembic commands ###
def
downgrade
():
# ### commands auto generated by Alembic - please adjust! ###
op
.
drop_column
(
'meetingreminders'
,
'additional_text'
)
# ### end Alembic commands ###
models/database.py
View file @
4f136efa
...
...
@@ -21,6 +21,7 @@ class ProtocolType(db.Model):
name
=
db
.
Column
(
db
.
String
,
unique
=
True
)
short_name
=
db
.
Column
(
db
.
String
,
unique
=
True
)
organization
=
db
.
Column
(
db
.
String
)
usual_time
=
db
.
Column
(
db
.
Time
)
is_public
=
db
.
Column
(
db
.
Boolean
)
private_group
=
db
.
Column
(
db
.
String
)
public_group
=
db
.
Column
(
db
.
String
)
...
...
@@ -36,12 +37,13 @@ class ProtocolType(db.Model):
reminders
=
relationship
(
"MeetingReminder"
,
backref
=
backref
(
"protocoltype"
),
cascade
=
"all, delete-orphan"
,
order_by
=
"MeetingReminder.days_before"
)
todos
=
relationship
(
"Todo"
,
backref
=
backref
(
"protocoltype"
),
order_by
=
"Todo.id"
)
def
__init__
(
self
,
name
,
short_name
,
organization
,
def
__init__
(
self
,
name
,
short_name
,
organization
,
usual_time
,
is_public
,
private_group
,
public_group
,
private_mail
,
public_mail
,
use_wiki
,
wiki_category
,
wiki_only_public
,
printer
):
self
.
name
=
name
self
.
short_name
=
short_name
self
.
organization
=
organization
self
.
usual_time
=
usual_time
self
.
is_public
=
is_public
self
.
private_group
=
private_group
self
.
public_group
=
public_group
...
...
@@ -56,11 +58,11 @@ class ProtocolType(db.Model):
return
(
"<ProtocolType(id={}, short_name={}, name={}, "
"organization={}, is_public={}, private_group={}, "
"public_group={}, use_wiki={}, wiki_category='{}', "
"wiki_only_public={}, printer={})>"
.
format
(
"wiki_only_public={}, printer={}
, usual_time={}
)>"
.
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
,
self
.
printer
))
self
.
wiki_only_public
,
self
.
printer
,
self
.
usual_time
))
def
get_latest_protocol
(
self
):
candidates
=
sorted
([
protocol
for
protocol
in
self
.
protocols
if
protocol
.
is_done
()],
key
=
lambda
p
:
p
.
date
,
reverse
=
True
)
...
...
@@ -385,12 +387,14 @@ class MeetingReminder(db.Model):
days_before
=
db
.
Column
(
db
.
Integer
)
send_public
=
db
.
Column
(
db
.
Boolean
)
send_private
=
db
.
Column
(
db
.
Boolean
)
additional_text
=
db
.
Column
(
db
.
String
)
def
__init__
(
self
,
protocoltype_id
,
days_before
,
send_public
,
send_private
):
def
__init__
(
self
,
protocoltype_id
,
days_before
,
send_public
,
send_private
,
additional_text
):
self
.
protocoltype_id
=
protocoltype_id
self
.
days_before
=
days_before
self
.
send_public
=
send_public
self
.
send_private
=
send_private
self
.
additional_text
=
additional_text
def
__repr__
(
self
):
return
"<MeetingReminder(id={}, protocoltype_id={}, days_before={}, send_public={}, send_private={})>"
.
format
(
...
...
requirements.txt
View file @
4f136efa
alembic
==0.8.10
amqp
==2.1.4
appdirs
==1.4.0
APScheduler
==3.3.1
argh
==0.26.2
billiard
==3.5.0.2
blessings
==1.6
...
...
@@ -37,6 +38,7 @@ regex==2017.2.8
requests
==2.13.0
six
==1.10.0
SQLAlchemy
==1.1.5
tzlocal
==1.3
vine
==1.1.3
watchdog
==0.8.3
wcwidth
==0.1.7
...
...
server.py
View file @
4f136efa
...
...
@@ -9,6 +9,10 @@ from flask_migrate import Migrate, MigrateCommand
#from flask_socketio import SocketIO
from
celery
import
Celery
from
sqlalchemy
import
or_
,
and_
from
apscheduler.schedulers.background
import
BackgroundScheduler
from
apscheduler.triggers.cron
import
CronTrigger
from
apscheduler.triggers.interval
import
IntervalTrigger
import
atexit
from
io
import
StringIO
,
BytesIO
import
os
from
datetime
import
datetime
...
...
@@ -39,6 +43,17 @@ celery = make_celery(app, config)
# return socketio
#socketio = make_socketio(app, config)
def
make_scheduler
(
app
,
config
,
function
):
scheduler
=
BackgroundScheduler
()
scheduler
.
start
()
scheduler
.
add_job
(
func
=
function
,
trigger
=
CronTrigger
(
hour
=
'*'
),
id
=
"scheduler"
,
name
=
"Do an action regularly"
,
replace_existing
=
True
)
atexit
.
register
(
scheduler
.
shutdown
)
app
.
jinja_env
.
trim_blocks
=
True
app
.
jinja_env
.
lstrip_blocks
=
True
app
.
jinja_env
.
filters
[
"datify"
]
=
date_filter
...
...
@@ -87,7 +102,7 @@ def new_type():
flash
(
"Du kannst keinen internen Protokolltypen anlegen, zu dem du selbst keinen Zugang hast."
,
"alert-error"
)
else
:
protocoltype
=
ProtocolType
(
form
.
name
.
data
,
form
.
short_name
.
data
,
form
.
organization
.
data
,
form
.
is_public
.
data
,
form
.
organization
.
data
,
form
.
usual_time
.
data
,
form
.
is_public
.
data
,
form
.
private_group
.
data
,
form
.
public_group
.
data
,
form
.
private_mail
.
data
,
form
.
public_mail
.
data
,
form
.
use_wiki
.
data
,
form
.
wiki_category
.
data
,
...
...
@@ -148,7 +163,7 @@ def new_reminder(type_id):
return
redirect
(
request
.
args
.
get
(
"next"
)
or
url_for
(
"index"
))
form
=
MeetingReminderForm
()
if
form
.
validate_on_submit
():
reminder
=
MeetingReminder
(
protocoltype
.
id
,
form
.
days_before
.
data
,
form
.
send_public
.
data
,
form
.
send_private
.
data
)
reminder
=
MeetingReminder
(
protocoltype
.
id
,
form
.
days_before
.
data
,
form
.
send_public
.
data
,
form
.
send_private
.
data
,
form
.
additional_text
.
data
)
db
.
session
.
add
(
reminder
)
db
.
session
.
commit
()
return
redirect
(
request
.
args
.
get
(
"next"
)
or
url_for
(
"show_type"
,
type_id
=
protocoltype
.
id
))
...
...
@@ -365,7 +380,7 @@ def list_protocols():
for
text
,
matched
in
parts
]))
search_results
[
protocol
]
=
"<br />
\n
"
.
join
(
formatted_lines
)
protocols
=
sorted
(
protocols
,
key
=
lambda
protocol
:
protocol
.
date
,
reverse
=
True
)
protocols
=
sorted
(
protocols
,
key
=
lambda
protocol
:
protocol
.
date
,
reverse
=
True
)
page
=
_get_page
()
page_count
=
int
(
math
.
ceil
(
len
(
protocols
))
/
config
.
PAGE_LENGTH
)
if
page
>=
page_count
:
...
...
@@ -809,6 +824,20 @@ def logout():
flash
(
"You are not logged in."
,
"alert-error"
)
return
redirect
(
url_for
(
".index"
))
def
check_and_send_reminders
():
with
app
.
app_context
():
current_time
=
datetime
.
now
()
current_day
=
current_time
.
date
()
for
protocol
in
Protocol
.
query
.
filter
(
Protocol
.
done
==
False
).
all
():
day_difference
=
(
protocol
.
date
-
current_day
).
days
usual_time
=
protocol
.
protocoltype
.
usual_time
protocol_time
=
datetime
(
1
,
1
,
1
,
usual_time
.
hour
,
usual_time
.
minute
)
hour_difference
=
(
protocol_time
-
current_time
).
seconds
//
3600
print
(
protocol
.
get_identifier
(),
day_difference
,
hour_difference
)
for
reminder
in
protocol
.
protocoltype
.
reminders
:
if
day_difference
==
reminder
.
days_before
and
hour_difference
==
0
:
tasks
.
send_reminder
(
reminder
,
protocol
)
if
__name__
==
"__main__"
:
make_scheduler
(
app
,
config
,
check_and_send_reminders
)
manager
.
run
()
tasks.py
View file @
4f136efa
...
...
@@ -5,7 +5,7 @@ import subprocess
import
shutil
import
tempfile
from
models.database
import
Document
,
Protocol
,
Error
,
Todo
,
Decision
,
TOP
,
DefaultTOP
from
models.database
import
Document
,
Protocol
,
Error
,
Todo
,
Decision
,
TOP
,
DefaultTOP
,
MeetingReminder
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
...
...
@@ -308,21 +308,33 @@ def print_file_async(filename, protocol_id):
db
.
session
.
add
(
error
)
db
.
session
.
commit
()
def
send_
mail
(
mai
l
):
send_
mail
_async
.
delay
(
mai
l
.
id
)
def
send_
reminder
(
reminder
,
protoco
l
):
send_
reminder
_async
.
delay
(
reminder
.
id
,
protoco
l
.
id
)
@
celery
.
task
def
send_
mail_async
(
mai
l_id
):
def
send_
reminder_async
(
reminder_id
,
protoco
l_id
):
with
app
.
app_context
():
mail
=
Mail
.
query
.
filter_by
(
id
=
mail_id
).
first
()
if
mail
is
None
:
return
False
mail
.
ready
=
False
mail
.
error
=
False
db
.
session
.
commit
()
result
=
mail_manager
.
send
(
mail
.
to_addr
,
mail
.
subject
,
mail
.
content
)
mail
.
ready
=
True
mail
.
error
=
not
result
db
.
session
.
commit
()
reminder
=
MeetingReminder
.
query
.
filter_by
(
id
=
reminder_id
).
first
()
protocol
=
Protocol
.
query
.
filter_by
(
id
=
protocol_id
).
first
()
reminder_text
=
render_template
(
"reminder.txt"
,
reminder
=
reminder
,
protocol
=
protocol
)
if
reminder
.
send_public
:
send_mail
(
protocol
,
protocol
.
protocoltype
.
public_mail
,
"Tagesordnung der {}"
.
format
(
protocol
.
protocoltype
.
name
),
reminder_text
)
if
reminder
.
send_private
:
send_mail
(
protocol
,
protocol
.
protocoltype
.
private_mail
,
"Tagesordnung der {}"
.
format
(
protocol
.
protocoltype
.
name
),
reminder_text
)
def
send_mail
(
protocol
,
to_addr
,
subject
,
content
):
if
to_addr
is
not
None
and
len
(
to_addr
.
strip
())
>
0
:
send_mail_async
.
delay
(
protocol
.
id
,
to_addr
,
subject
,
content
)
@
celery
.
task
def
send_mail_async
(
protocol_id
,
to_addr
,
subject
,
content
):
with
app
.
app_context
():
protocol
=
Protocol
.
query
.
filter_by
(
id
=
protocol_id
).
first
()
try
:
print
(
"sending {} to {}"
.
format
(
subject
,
to_addr
))
mail_manager
.
send
(
to_addr
,
subject
,
content
)
except
Exception
as
exc
:
error
=
protocol
.
create_error
(
"Sending Mail"
,
"Sending mail failed"
,
str
(
exc
))
db
.
session
.
add
(
error
)
db
.
session
.
commit
()
templates/reminder.txt
0 → 100644
View file @
4f136efa
Die nächste {{protocol.protocoltype.name}} findet am {{protocol.date|datify}} um {{protocol.protocoltype.usual_time|timify}} statt.
Die vorläufige Tagesordnung ist:
{% if not protocol.has_nonplanned_tops() %}
{% for default_top in protocol.protocoltype.default_tops %}
{% if not default_top.is_at_end() %}
* {{default_top.name}}
{% endif %}
{% endfor %}
{% endif %}
{% for top in protocol.tops %}
* {{top.name }}
{% endfor %}
{% if not protocol.has_nonplanned_tops() %}
{% for default_top in protocol.protocoltype.default_tops %}
{% if default_top.is_at_end() %}
* {{default_top.name}}
{% endif %}
{% endfor %}
{% endif %}
{% if reminder.additional_text is not none %}
{{reminder.additional_text}}
{% endif %}
utils.py
View file @
4f136efa
...
...
@@ -67,7 +67,6 @@ class MailManager:
self
.
hostname
=
getattr
(
config
,
"MAIL_HOST"
,
""
)
self
.
username
=
getattr
(
config
,
"MAIL_USER"
,
""
)
self
.
password
=
getattr
(
config
,
"MAIL_PASSWORD"
,
""
)
self
.
prefix
=
getattr
(
config
,
"MAIL_PREFIX"
,
""
)
def
send
(
self
,
to_addr
,
subject
,
content
):
if
(
not
self
.
active
...
...
@@ -75,21 +74,16 @@ class MailManager:
or
not
self
.
username
or
not
self
.
password
or
not
self
.
from_addr
):
return
True
try
:
msg
=
MIMEMultipart
(
"alternative"
)
msg
[
"From"
]
=
self
.
from_addr
msg
[
"To"
]
=
to_addr
msg
[
"Subject"
]
=
"[{}] {}"
.
format
(
self
.
prefix
,
subject
)
if
self
.
prefix
else
subject
msg
.
attach
(
MIMEText
(
content
,
_charset
=
"utf-8"
))
server
=
smtplib
.
SMTP_SSL
(
self
.
hostname
)
server
.
login
(
self
.
username
,
self
.
password
)
server
.
sendmail
(
self
.
from_addr
,
to_addr
,
msg
.
as_string
())
server
.
quit
()
except
Exception
as
e
:
print
(
e
)
return
False
return
True
return
msg
=
MIMEMultipart
(
"alternative"
)
msg
[
"From"
]
=
self
.
from_addr
msg
[
"To"
]
=
to_addr
msg
[
"Subject"
]
=
subject
msg
.
attach
(
MIMEText
(
content
,
_charset
=
"utf-8"
))
server
=
smtplib
.
SMTP_SSL
(
self
.
hostname
)
server
.
login
(
self
.
username
,
self
.
password
)
server
.
sendmail
(
self
.
from_addr
,
to_addr
,
msg
.
as_string
())
server
.
quit
()
mail_manager
=
MailManager
(
config
)
...
...
views/forms.py
View file @
4f136efa
from
flask_wtf
import
FlaskForm
from
wtforms
import
StringField
,
PasswordField
,
BooleanField
,
DateField
,
HiddenField
,
IntegerField
,
SelectField
,
FileField
,
DateTimeField
from
wtforms
import
StringField
,
PasswordField
,
BooleanField
,
DateField
,
HiddenField
,
IntegerField
,
SelectField
,
FileField
,
DateTimeField
,
TextAreaField
from
wtforms.validators
import
InputRequired
,
Optional
import
config
...
...
@@ -12,6 +12,7 @@ class ProtocolTypeForm(FlaskForm):
name
=
StringField
(
"Name"
,
validators
=
[
InputRequired
(
"Du musst einen Namen angeben."
)])
short_name
=
StringField
(
"Abkürzung"
,
validators
=
[
InputRequired
(
"Du musst eine Abkürzung angebene."
)])
organization
=
StringField
(
"Organisation"
,
validators
=
[
InputRequired
(
"Du musst eine zugehörige Organisation angeben."
)])
usual_time
=
DateTimeField
(
"Üblicher Beginn"
,
validators
=
[
InputRequired
(
"Bitte gib die Zeit an, zu der die Sitzung beginnt."
)],
format
=
"%H:%M"
)
is_public
=
BooleanField
(
"Öffentlich sichtbar"
)
private_group
=
StringField
(
"Interne Gruppe"
)
public_group
=
StringField
(
"Öffentliche Gruppe"
)
...
...
@@ -30,6 +31,7 @@ class MeetingReminderForm(FlaskForm):
days_before
=
IntegerField
(
"Tage vor Sitzung"
,
validators
=
[
InputRequired
(
"Du musst eine Dauer angeben."
)])
send_public
=
BooleanField
(
"Öffentlich einladen"
)
send_private
=
BooleanField
(
"Intern einladen"
)
additional_text
=
TextAreaField
(
"Zusätzlicher Mailinhalt"
)
class
NewProtocolForm
(
FlaskForm
):
protocoltype
=
SelectField
(
"Typ"
,
choices
=
[],
coerce
=
int
)
...
...
views/tables.py
View file @
4f136efa
...
...
@@ -105,8 +105,8 @@ class ProtocolTypeTable(SingleValueTable):
super
().
__init__
(
protocoltype
.
name
,
protocoltype
,
newlink
=
url_for
(
"edit_type"
,
type_id
=
protocoltype
.
id
))
def
headers
(
self
):
headers
=
[
"Name"
,
"Abkürzung"
,
"Organisation"
,
"
Öffentlich
"
,
"Interne Gruppe"
,
"Öffentliche Gruppe"
,
headers
=
[
"Name"
,
"Abkürzung"
,
"Organisation"
,
"
Beginn
"
,
"Öffentlich"
,
"Interne Gruppe"
,
"Öffentliche Gruppe"
,
"Interner Verteiler"
,
"Öffentlicher Verteiler"
,
"Drucker"
,
"Wiki"
]
if
self
.
value
.
use_wiki
:
...
...
@@ -118,6 +118,7 @@ class ProtocolTypeTable(SingleValueTable):
self
.
value
.
name
,
self
.
value
.
short_name
,
self
.
value
.
organization
,
self
.
value
.
usual_time
.
strftime
(
"%H:%M"
)
if
self
.
value
.
usual_time
is
not
None
else
""
,
# todo: remove if, this field is required
Table
.
bool
(
self
.
value
.
is_public
),
self
.
value
.
private_group
,
self
.
value
.
public_group
,
...
...
@@ -156,12 +157,13 @@ class MeetingRemindersTable(Table):
self
.
protocoltype
=
protocoltype
def
headers
(
self
):
return
[
"Zeit"
,
"Einladen"
,
""
]
return
[
"Zeit"
,
"Einladen"
,
"Zusätzlicher Mailinhalt"
,
""
]
def
row
(
self
,
reminder
):
return
[
"{} Tage"
.
format
(
reminder
.
days_before
),
self
.
get_send_summary
(
reminder
),
reminder
.
additional_text
or
""
,
Table
.
concat
([
Table
.
link
(
url_for
(
"edit_reminder"
,
type_id
=
self
.
protocoltype
.
id
,
reminder_id
=
reminder
.
id
),
"Ändern"
),
Table
.
link
(
url_for
(
"delete_reminder"
,
type_id
=
self
.
protocoltype
.
id
,
reminder_id
=
reminder
.
id
),
"Löschen"
,
confirm
=
"Bist du dir sicher, dass du die Einladungsmail {} Tage vor der Sitzung löschen willst?"
.
format
(
reminder
.
days_before
))
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment