mirror of
https://github.com/FAUSheppy/flask-json-dream-website
synced 2025-12-06 08:11:35 +01:00
refactor
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
js/
|
js/
|
||||||
|
__pycache__/
|
||||||
css/
|
css/
|
||||||
*.swp
|
*.swp
|
||||||
*.jpg
|
*.jpg
|
||||||
|
|||||||
42
README.md
42
README.md
@@ -1,50 +1,14 @@
|
|||||||
# Requirements
|
# Requirements
|
||||||
This Softwares runs python3-flask with markdown, json and caldav.
|
This Softwares runs python3-flask with markdown, json and caldav.
|
||||||
|
|
||||||
python3 -m pip install flask, json, caldav, markdown2
|
python3 -m pip install flask, caldav, markdown2
|
||||||
|
|
||||||
This Software requires bootstrap > 4.13 which can be downloaded [here](https://getbootstrap.com/docs/4.3/getting-started/download/). It must be unpacked into the *static/*-directory into *js* and *css* respectively.
|
This Software requires bootstrap > 4.13 which can be downloaded [here](https://getbootstrap.com/docs/4.3/getting-started/download/). It must be unpacked into the *static/*-directory into *js* and *css* respectively.
|
||||||
|
|
||||||
Additionally bootstrap depends on [jquery](https://code.jquery.com) which you have to download into a file called *jquery.min.js* in *static/js/*.
|
Additionally bootstrap depends on [jquery](https://code.jquery.com) which you have to download into a file called *jquery.min.js* in *static/js/*.
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
./server.py -h
|
|
||||||
usage: server.py [-h] [-i INTERFACE] [-p PORT] --cal-info CAL_INFO
|
|
||||||
[--no-update-on-start]
|
|
||||||
|
|
||||||
optional arguments:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
-i INTERFACE Interface to listen on (default: 0.0.0.0)
|
|
||||||
-p PORT, --port PORT Port to listen on (default: 5000)
|
|
||||||
--cal-info CAL_INFO File Containing a public calendar link (default: None)
|
|
||||||
--no-update-on-start Don't update the calendar on start (default: False)
|
|
||||||
|
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
The page and most of it's content is configured via json. To use the CalDav-events section, you need to add a comma seperated file with the following format format/information:
|
The page and most of it's content is configured via json, basic configuration is done in *config.py*.
|
||||||
|
|
||||||
URL,USER,PASSWORD
|
|
||||||
|
|
||||||
## Main Config
|
|
||||||
The main Config ``config.json`` which must be placed in the project-root must contain the following values:
|
|
||||||
|
|
||||||
{
|
|
||||||
"siteTitle" : "the default site title",
|
|
||||||
"siteDescription" : "a description for this site",
|
|
||||||
"siteLogo" : "url to logo",
|
|
||||||
"siteURL": "the url of this site"
|
|
||||||
}
|
|
||||||
|
|
||||||
Additionally it may contain the following information:
|
|
||||||
|
|
||||||
"teamspeak-server" : "TS_SERVER",
|
|
||||||
"discord-server" : "DISCORD_LINK",
|
|
||||||
"facebook" : "FACEBOOK_LINK",
|
|
||||||
"instagram" : "INSTAGRAM_LINK",
|
|
||||||
"twitter" : "TWITTER_LINK",
|
|
||||||
"twitch-channel" : "TWITCH_CHANNEL_NAME",
|
|
||||||
"twitch-placeholder-img" : "PLACEHOLDER_IMG"
|
|
||||||
|
|
||||||
## Startpage Sections
|
## Startpage Sections
|
||||||
### Events
|
### Events
|
||||||
@@ -108,4 +72,4 @@ New subpages must be added as a new location in the *server.py* like this:
|
|||||||
def subpage():
|
def subpage():
|
||||||
return flask.render_template("subpage.html", conf=mainConfig)
|
return flask.render_template("subpage.html", conf=mainConfig)
|
||||||
|
|
||||||
See the example subpage-templates in *templates/*.
|
See the example *subpage\_example.html* in *templates/*.
|
||||||
|
|||||||
4
app.py
Normal file
4
app.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import server as moduleContainingApp
|
||||||
|
|
||||||
|
def createApp(envivorment=None, start_response=None):
|
||||||
|
return moduleContainingApp.app
|
||||||
31
config.py
Normal file
31
config.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# calendar configuration #
|
||||||
|
SHOW_CALENDAR=False
|
||||||
|
CALENDAR_URL=""
|
||||||
|
CALENDAR_USERNAME=""
|
||||||
|
CALENDAR_PASSWORD=""
|
||||||
|
|
||||||
|
# content directory #
|
||||||
|
CONTENT_DIR="content.example"
|
||||||
|
|
||||||
|
# reload calendar on start #
|
||||||
|
RELOAD_CALENDAR_ON_START=True
|
||||||
|
|
||||||
|
# other
|
||||||
|
NEWS_MAX_AGE=90
|
||||||
|
SITEMAP_IGNORE = ["icon", "siteMap", "invalidate", "news"]
|
||||||
|
|
||||||
|
# site parameters
|
||||||
|
SITE_TITLE = "Site Title"
|
||||||
|
SITE_DESCRIPTION = "Site Description"
|
||||||
|
SITE_AUTHOR = "Site Author"
|
||||||
|
SITE_LOGO_URL = "Site Logo URL"
|
||||||
|
SITE_BASE_URL = "Site Base URL"
|
||||||
|
|
||||||
|
TEAMSPEAK_SERVER = "teamspeak.com"
|
||||||
|
DISCORD_SERVER = "https://discord.gg/",
|
||||||
|
FACEBOOK = "https://www.facebook.com/",
|
||||||
|
INSTAGRAM = "https://www.instagram.com/",
|
||||||
|
TWITTER = "https://twitter.com/its_a_sheppy",
|
||||||
|
|
||||||
|
TWITCH_CHANNEL = "esports_erlangen",
|
||||||
|
TWITCH_PLACEHOLDER_IMG = "placeholder.png"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"picture" : "/static/pictures/placeholder.png",
|
"picture" : "/pictures/placeholder.png",
|
||||||
"title" : "Section Title",
|
"title" : "Section Title",
|
||||||
"text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
"text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||||
"moreInfoButtonText" : "Mehr..",
|
"moreInfoButtonText" : "Mehr..",
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"picture" : "/static/pictures/placeholder.png",
|
"picture" : "/pictures/placeholder.png",
|
||||||
"title" : "Section Title",
|
"title" : "Section Title",
|
||||||
"text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
"text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"picture" : "/static/pictures/placeholder.png",
|
"picture" : "/pictures/placeholder.png",
|
||||||
"title" : "Section Title",
|
"title" : "Section Title",
|
||||||
"text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
"text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||||
"moreInfoButtonText" : "Mehr..",
|
"moreInfoButtonText" : "Mehr..",
|
||||||
189
server.py
189
server.py
@@ -8,40 +8,55 @@ import caldav
|
|||||||
import datetime as dt
|
import datetime as dt
|
||||||
import markdown2
|
import markdown2
|
||||||
|
|
||||||
# sitemap utilities #
|
# sitemap utilities
|
||||||
from werkzeug.routing import BuildError
|
from werkzeug.routing import BuildError
|
||||||
import xml.etree.ElementTree as et
|
import xml.etree.ElementTree as et
|
||||||
|
|
||||||
VEREIN_SECTIONS_DIR = "sections/"
|
# paths
|
||||||
MAIN_LINKS_DIR = "main-links/"
|
SECTIONS_DIR = "sections/"
|
||||||
NEWS_DIR = "news/"
|
NEWS_DIR = "news/"
|
||||||
|
PICTURES_DIR = "pictures/"
|
||||||
|
|
||||||
app = flask.Flask("athq-landing-page", static_folder=None)
|
# json config keys
|
||||||
mainConfig = dict()
|
TIMEOUT_RELATIVE = "timeout-relative-weeks"
|
||||||
with open("config.json") as f:
|
TIMEOUT_FIXED = "timeout-fixed"
|
||||||
mainConfig = json.load(f)
|
PARSED_TIME = "parsed-time"
|
||||||
|
ACTIVE = "active"
|
||||||
|
DATE = "date"
|
||||||
|
UID = "uid"
|
||||||
|
|
||||||
caldavUrl = None
|
MARKDOWN_FILE_KEY = "markdown-file"
|
||||||
caldavPassword = None
|
MARKDOWN_CONTENT_KEY = "markdown-content"
|
||||||
caldavUsername = None
|
|
||||||
|
# sitemap
|
||||||
|
PRIORITY_PRIMARY = 1.0
|
||||||
|
PRIORITY_SECONDARY = 0.8
|
||||||
|
|
||||||
|
# other
|
||||||
|
HTTP_NOT_FOUND = 404
|
||||||
|
EMPTY_STRING = ""
|
||||||
|
CACHE_FILE = "cache.json"
|
||||||
|
READ = "r"
|
||||||
|
WRITE = "w"
|
||||||
|
|
||||||
|
app = flask.Flask("FLASK_JSON_DREAM_WEBSITE", static_folder=None)
|
||||||
|
app.config.from_object("config")
|
||||||
|
|
||||||
def updateEventsFromCalDav():
|
def updateEventsFromCalDav():
|
||||||
|
'''Load event from a remote calendar'''
|
||||||
|
|
||||||
|
if app.config["SHOW_CALENDAR"]:
|
||||||
if app.config["USE_CALENDAR"]:
|
|
||||||
client = caldav.DAVClient(url=caldavUrl, username=caldavUsername, password=caldavPassword)
|
client = caldav.DAVClient(url=caldavUrl, username=caldavUsername, password=caldavPassword)
|
||||||
authenticatedClient = client.principal()
|
authenticatedClient = client.principal()
|
||||||
defaultCal = authenticatedClient.calendars()[0]
|
defaultCal = authenticatedClient.calendars()[0]
|
||||||
|
|
||||||
start = dt.datetime.now()
|
start = dt.datetime.now()
|
||||||
start = start - dt.timedelta(seconds=start.timestamp() % dt.timedelta(days=1).total_seconds())
|
start -= dt.timedelta(seconds=start.timestamp() % dt.timedelta(days=1).total_seconds())
|
||||||
end = start + dt.timedelta(days=90)
|
end = start + dt.timedelta(days=app.config["NEWS_MAX_AGE"])
|
||||||
|
|
||||||
# TODO remove this
|
|
||||||
# start = start - dt.timedelta(days=90)
|
|
||||||
|
|
||||||
events = sorted(defaultCal.date_search(start, end),
|
events = sorted(defaultCal.date_search(start, end),
|
||||||
key=lambda e: e.vobject_instance.vevent.dtstart.value)
|
key=lambda e: e.vobject_instance.vevent.dtstart.value)
|
||||||
|
|
||||||
eventsDictList = []
|
eventsDictList = []
|
||||||
for e in events:
|
for e in events:
|
||||||
date = e.vobject_instance.vevent.dtstart.value
|
date = e.vobject_instance.vevent.dtstart.value
|
||||||
@@ -59,17 +74,20 @@ def updateEventsFromCalDav():
|
|||||||
else:
|
else:
|
||||||
eventsDictList = []
|
eventsDictList = []
|
||||||
|
|
||||||
with open("cache.json", "w") as f:
|
# dump to cache file #
|
||||||
|
with open(CACHE_FILE, WRITE) as f:
|
||||||
json.dump(eventsDictList, f)
|
json.dump(eventsDictList, f)
|
||||||
|
|
||||||
|
|
||||||
def getEventsCache():
|
def getEventsCache():
|
||||||
with open("cache.json", "r") as f:
|
'''Return the cached events'''
|
||||||
|
|
||||||
|
with open(CACHE_FILE, READ) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
def readJsonDir(basedir):
|
def readJsonDir(basedir):
|
||||||
|
'''Read a directory containing json information'''
|
||||||
|
|
||||||
# load json files from projects/ dir #
|
|
||||||
jsonDictList =[]
|
jsonDictList =[]
|
||||||
for root, dirs, files in os.walk(basedir):
|
for root, dirs, files in os.walk(basedir):
|
||||||
for filename in sorted(files):
|
for filename in sorted(files):
|
||||||
@@ -80,14 +98,10 @@ def readJsonDir(basedir):
|
|||||||
return jsonDictList
|
return jsonDictList
|
||||||
|
|
||||||
def parseNewsDirWithTimeout():
|
def parseNewsDirWithTimeout():
|
||||||
|
'''Parse a directory containing news-json structs and filter out
|
||||||
|
entries that have exceeded the max age'''
|
||||||
|
|
||||||
TIMEOUT_RELATIVE = "timeout-relative-weeks"
|
news = readJsonDir(app.config["NEWS_DIR"])
|
||||||
TIMEOUT_FIXED = "timeout-fixed"
|
|
||||||
PARSED_TIME = "parsed-time"
|
|
||||||
ACTIVE = "active"
|
|
||||||
DATE = "date"
|
|
||||||
|
|
||||||
news = readJsonDir(NEWS_DIR)
|
|
||||||
now = dt.datetime.now()
|
now = dt.datetime.now()
|
||||||
for n in news:
|
for n in news:
|
||||||
n.update( { PARSED_TIME : dt.datetime.fromtimestamp(n[DATE]) } )
|
n.update( { PARSED_TIME : dt.datetime.fromtimestamp(n[DATE]) } )
|
||||||
@@ -104,96 +118,107 @@ def parseNewsDirWithTimeout():
|
|||||||
|
|
||||||
return sorted(news, key=lambda n: n[PARSED_TIME], reverse=True)
|
return sorted(news, key=lambda n: n[PARSED_TIME], reverse=True)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/invalidate")
|
@app.route("/invalidate")
|
||||||
def invalidateEventCache():
|
def invalidateEventCache():
|
||||||
|
'''Reload the calendar events'''
|
||||||
|
|
||||||
updateEventsFromCalDav();
|
updateEventsFromCalDav();
|
||||||
return ("", 204)
|
return (EMPTY_STRING, 204)
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def root():
|
def root():
|
||||||
announcements = parseNewsDirWithTimeout()
|
return flask.render_template("index.html", conf=app.config,
|
||||||
return flask.render_template("index.html", mainLinks=readJsonDir(MAIN_LINKS_DIR),
|
|
||||||
siteTitle=mainConfig["siteTitle"],
|
|
||||||
conf=mainConfig,
|
|
||||||
events=getEventsCache(),
|
events=getEventsCache(),
|
||||||
moreEvents=len(getEventsCache())>3,
|
moreEvents=len(getEventsCache())>3,
|
||||||
vereinSections=readJsonDir(VEREIN_SECTIONS_DIR),
|
sections=readJsonDir(app.config["SECTIONS_DIR"]),
|
||||||
announcements=announcements)
|
announcements=parseNewsDirWithTimeout())
|
||||||
|
|
||||||
@app.route("/impressum")
|
@app.route("/impressum")
|
||||||
def impressum():
|
def impressum():
|
||||||
return flask.render_template("impressum.html", conf=mainConfig)
|
return flask.render_template("impressum.html", conf=app.config)
|
||||||
|
|
||||||
@app.route("/verein")
|
|
||||||
def verein():
|
|
||||||
return flask.render_template("verein.html", conf=mainConfig)
|
|
||||||
|
|
||||||
@app.route("/stammtisch")
|
|
||||||
def stammtisch():
|
|
||||||
return flask.render_template("stammtisch.html", conf=mainConfig)
|
|
||||||
|
|
||||||
@app.route("/people")
|
@app.route("/people")
|
||||||
def people():
|
def people():
|
||||||
return flask.render_template("people.html", conf=mainConfig,
|
return flask.render_template("people.html", conf=app.config,
|
||||||
people=readJsonDir("people/"))
|
people=readJsonDir("people/"))
|
||||||
|
|
||||||
@app.route("/news")
|
@app.route("/news")
|
||||||
def news():
|
def news():
|
||||||
|
'''Display news-articles based on a UID-parameter'''
|
||||||
|
|
||||||
uid = flask.request.args.get("uid")
|
requestedId = flask.request.args.get(UID)
|
||||||
|
|
||||||
|
# load news and map UIDs #
|
||||||
news = parseNewsDirWithTimeout()
|
news = parseNewsDirWithTimeout()
|
||||||
newsDict = dict()
|
newsDict = dict()
|
||||||
for n in news:
|
for n in news:
|
||||||
newsDict.update( { n["uid"] : n } )
|
newsDict.update( { n[UID] : n } )
|
||||||
|
|
||||||
if not uid:
|
# set newest article config if there is not UID #
|
||||||
article = sorted(news, key=lambda n: n["parsed-time"])[-1]
|
# return 404 if the UID doesnt exist #
|
||||||
elif not newsDict[int(uid)]:
|
# set article config of matching article otherwiese #
|
||||||
return ("", 404)
|
if not requestedId:
|
||||||
|
article = sorted(news, key=lambda n: n[PARSED_TIME])[-1]
|
||||||
|
elif not newsDict[int(requestedId)]:
|
||||||
|
return (EMPTY_STRING, HTTP_NOT_FOUND)
|
||||||
else:
|
else:
|
||||||
article = newsDict[int(uid)]
|
article = newsDict[int(requestedId)]
|
||||||
try:
|
|
||||||
with open(article["markdown-file"]) as f:
|
|
||||||
article.update( { "markdown-content" : markdown2.markdown(f.read()) } )
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
return ("File not found Error ({})".format(e), 404)
|
|
||||||
|
|
||||||
return flask.render_template("news.html", conf=mainConfig, article=article)
|
# load article based on config #
|
||||||
|
try:
|
||||||
|
with open(article[MARKDOWN_FILE_KEY]) as f:
|
||||||
|
article.update( { MARKDOWN_CONTENT_KEY : markdown2.markdown(f.read()) } )
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
return ("File not found Error ({})".format(e), HTTP_NOT_FOUND)
|
||||||
|
|
||||||
|
return flask.render_template("news.html", conf=app.config, article=article)
|
||||||
|
|
||||||
@app.route("/static/<path:path>")
|
@app.route("/static/<path:path>")
|
||||||
def sendStatic(path):
|
def sendStatic(path):
|
||||||
if "pictures" in path:
|
|
||||||
cache_timeout = 2592000
|
|
||||||
else:
|
|
||||||
cache_timeout = None
|
cache_timeout = None
|
||||||
return flask.send_from_directory('static', path, cache_timeout=cache_timeout)
|
return flask.send_from_directory('static', path, cache_timeout=cache_timeout)
|
||||||
|
|
||||||
|
@app.route("/picture/<path:path>")
|
||||||
|
def sendPicture(path):
|
||||||
|
cache_timeout = 2592000
|
||||||
|
return flask.send_from_directory(PICTURES_DIR, path, cache_timeout=cache_timeout)
|
||||||
|
|
||||||
@app.route('/defaultFavicon.ico')
|
@app.route('/defaultFavicon.ico')
|
||||||
def icon():
|
def icon():
|
||||||
return flask.send_from_directory('static', 'defaultFavicon.ico')
|
return flask.send_from_directory('static', 'defaultFavicon.ico')
|
||||||
|
|
||||||
@app.route("/sitemap.xml")
|
@app.route("/sitemap.xml")
|
||||||
def siteMap():
|
def siteMap():
|
||||||
|
'''Return an XML-sitemap for SEO'''
|
||||||
|
|
||||||
|
# search for urls to add to sitemap #
|
||||||
urls = []
|
urls = []
|
||||||
|
|
||||||
|
# iterate through all endpoints #
|
||||||
for rule in app.url_map.iter_rules():
|
for rule in app.url_map.iter_rules():
|
||||||
skips = ["icon", "siteMap", "invalidate", "news"]
|
|
||||||
if any([s in rule.endpoint for s in skips]):
|
# skip all endpoints #
|
||||||
|
if any([s in rule.endpoint for s in app.config["SITEMAP_IGNORE"]]):
|
||||||
continue
|
continue
|
||||||
if "GET" in rule.methods:
|
|
||||||
|
# skip all non-GET endpoints #
|
||||||
|
if not "GET" in rule.methods:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# get url for endpoint, get start time and set priority #
|
||||||
try:
|
try:
|
||||||
url = flask.url_for(rule.endpoint, **(rule.defaults or {}))
|
url = flask.url_for(rule.endpoint, **(rule.defaults or {}))
|
||||||
priority = 0.8
|
priority = PRIORITY_SECONDARY
|
||||||
if rule.endpoint == "root":
|
if rule.endpoint == "root":
|
||||||
priority = 1.0
|
priority = PRIORITY_PRIMARY
|
||||||
urls += [(url, app.config["START_TIME"], priority)]
|
urls += [(url, app.config["START_TIME"], priority)]
|
||||||
except BuildError:
|
except BuildError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# add news articles to sitemap #
|
||||||
news = parseNewsDirWithTimeout()
|
news = parseNewsDirWithTimeout()
|
||||||
for n in filter(lambda x: x["active"], news):
|
for n in filter(lambda x: x["active"], news):
|
||||||
urls += [("/news?uid={}".format(n["uid"]), n["parsed-time"], 0.8)]
|
urls += [("/news?uid={}".format(n[UID]), n[PARSED_TIME], PRIORITY_SECONDARY)]
|
||||||
|
|
||||||
hostname = flask.request.headers.get("X-REAL-HOSTNAME")
|
hostname = flask.request.headers.get("X-REAL-HOSTNAME")
|
||||||
if not hostname:
|
if not hostname:
|
||||||
@@ -215,32 +240,30 @@ def siteMap():
|
|||||||
xmlDump += et.tostring(top, encoding='UTF-8', method='xml').decode()
|
xmlDump += et.tostring(top, encoding='UTF-8', method='xml').decode()
|
||||||
return flask.Response(xmlDump, mimetype='application/xml')
|
return flask.Response(xmlDump, mimetype='application/xml')
|
||||||
|
|
||||||
|
@app.before_first_request
|
||||||
|
def init():
|
||||||
|
app.config["SECTIONS_DIR"] = os.path.join(app.config["CONTENT_DIR"], SECTIONS_DIR)
|
||||||
|
app.config["NEWS_DIR"] = os.path.join(app.config["CONTENT_DIR"], NEWS_DIR)
|
||||||
|
|
||||||
|
if app.config["RELOAD_CALENDAR_ON_START"]:
|
||||||
|
updateEventsFromCalDav()
|
||||||
|
|
||||||
|
app.config["START_TIME"] = dt.datetime.now()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Projects Showcase',
|
parser = argparse.ArgumentParser(description='Projects Showcase',
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
|
||||||
# general parameters #
|
# general parameters #
|
||||||
parser.add_argument("-i", "--interface", default="0.0.0.0", help="Interface to listen on")
|
parser.add_argument("-i", "--interface", default="127.0.0.1", help="Interface to listen on")
|
||||||
parser.add_argument("-p", "--port", default="5000", help="Port to listen on")
|
parser.add_argument("-p", "--port", default="5000", help="Port to listen on")
|
||||||
parser.add_argument("--cal-info", help="File Containing a public calendar link")
|
parser.add_argument("--auto-reload", action="store_const", default=False, const=True,
|
||||||
|
help="Automaticly reload HTTP templates (impacts performance)")
|
||||||
parser.add_argument("--no-update-on-start", action="store_const", const=True, default=False,
|
parser.add_argument("--no-update-on-start", action="store_const", const=True, default=False,
|
||||||
help="Don't update the calendar on start")
|
help="Don't update the calendar on start")
|
||||||
|
|
||||||
# startup #
|
# startup #
|
||||||
app.config['TEMPLATES_AUTO_RELOAD'] = True
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
app.config['TEMPLATES_AUTO_RELOAD'] = args.auto_reload
|
||||||
if args.cal_info:
|
|
||||||
app.config["USE_CALENDAR"] = True
|
|
||||||
with open(args.cal_info) as f:
|
|
||||||
caldavUrl, caldavUsername, caldavPassword = f.read().strip().split(",")
|
|
||||||
else:
|
|
||||||
app.config["USE_CALENDAR"] = False
|
|
||||||
|
|
||||||
if not args.no_update_on_start:
|
|
||||||
updateEventsFromCalDav()
|
|
||||||
|
|
||||||
app.config["START_TIME"] = dt.datetime.now()
|
|
||||||
|
|
||||||
app.run(host=args.interface, port=args.port)
|
app.run(host=args.interface, port=args.port)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -1 +0,0 @@
|
|||||||
placeholder.png
|
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<meta name="description" content="">
|
<meta name="description" content="{{ conf['SITE_DESCRIPTION'] }}">
|
||||||
<meta name="author" content="">
|
<meta name="author" content="{{ conf['SITE_AUTHOR'] }}">
|
||||||
|
<meta name="title" content="{{ conf['SITE_TITLE'] }}">
|
||||||
|
|
||||||
<link rel="shortcut icon" href="/defaultFavicon.ico">
|
<link rel="shortcut icon" href="/defaultFavicon.ico">
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
<!-- Bootstrap core CSS -->
|
||||||
@@ -14,16 +17,16 @@
|
|||||||
<script src="/static/js/bootstrap.min.js"></script>
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:title" content="{{ conf['siteTitle'] }}" />
|
<meta property="og:title" content="{{ conf['SITE_TITLE'] }}" />
|
||||||
<meta property="og:description" content="{{ conf['siteDescription'] }}" />
|
<meta property="og:description" content="{{ conf['SITE_DESCRIPTION'] }}" />
|
||||||
<meta property="og:url" content="{{ url_for(request.endpoint) }}" />
|
<meta property="og:url" content="{{ url_for(request.endpoint) }}" />
|
||||||
<meta property="og:image" content="{{ conf['siteLogo'] }}">
|
<meta property="og:image" content="{{ conf['SITE_LOG_URL'] }}">
|
||||||
|
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "Organization",
|
"@type": "Organization",
|
||||||
"url": "{{ conf['siteURL'] }}",
|
"url": "{{ conf['SITE_BASE_URL'] }}",
|
||||||
"logo": "{{ conf['siteLogo'] }}"
|
"logo": "{{ conf['SITE_LOGO_URL'] }}"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
{% include 'head.html' %}
|
{% include 'head.html' %}
|
||||||
|
|
||||||
<title>{{ conf["siteTitle"] }}</title>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color: #eae9e9">
|
<body style="background-color: #eae9e9">
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
{% include 'head.html' %}
|
{% include 'head.html' %}
|
||||||
|
|
||||||
<title>{{ conf["siteTitle"] }}</title>
|
|
||||||
|
|
||||||
<!-- Load the Twitch embed script -->
|
<!-- Load the Twitch embed script -->
|
||||||
<!-- <script src="https://embed.twitch.tv/embed/v1.js"></script> -->
|
<!-- <script src="https://embed.twitch.tv/embed/v1.js"></script> -->
|
||||||
<script src="https://sslrelay.atlantishq.de/twitch"></script>
|
<script src="https://sslrelay.atlantishq.de/twitch"></script>
|
||||||
@@ -55,7 +53,7 @@
|
|||||||
|
|
||||||
<div id="twitch-consent-placeholder" class="card bg-dark text-white">
|
<div id="twitch-consent-placeholder" class="card bg-dark text-white">
|
||||||
<img style="min-width: 80%; min-height: 200px;"
|
<img style="min-width: 80%; min-height: 200px;"
|
||||||
class="card-img" src="/static/pictures/{{ conf['twitch-placeholder-img'] }}" >
|
class="card-img" src="/pictures/{{ conf['twitch-placeholder-img'] }}" >
|
||||||
<div class="card-img-overlay">
|
<div class="card-img-overlay">
|
||||||
<label class="switch mt-3 mt-0-u440">
|
<label class="switch mt-3 mt-0-u440">
|
||||||
<input id="toogle-twitch" class="custom-control-input"
|
<input id="toogle-twitch" class="custom-control-input"
|
||||||
@@ -97,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for section in vereinSections %}
|
{% for section in sections %}
|
||||||
<div class="{% if loop.index %2 == 1 %} bg-secondary {% else %} bg-dark {% endif %} pt-2 pb-2">
|
<div class="{% if loop.index %2 == 1 %} bg-secondary {% else %} bg-dark {% endif %} pt-2 pb-2">
|
||||||
<div class="container text-color-special">
|
<div class="container text-color-special">
|
||||||
<div class="row" {% if loop.index %2 == 1 %} style="flex-direction: row-reverse;" {% endif %}>
|
<div class="row" {% if loop.index %2 == 1 %} style="flex-direction: row-reverse;" {% endif %}>
|
||||||
|
|||||||
@@ -15,15 +15,15 @@
|
|||||||
<a class="nav-link" href="/">Home</a>
|
<a class="nav-link" href="/">Home</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if conf["teamspeak-server"] %}
|
{% if conf["TEAMSPEAK_SERVER"] %}
|
||||||
<li class="nav-item right">
|
<li class="nav-item right">
|
||||||
<a class="nav-link" href="ts3server://{{ conf['teamspeak-server'] }}">Teamspeak</a>
|
<a class="nav-link" href="ts3server://{{ conf['TEAMSPEAK_SERVER'] }}">Teamspeak</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if conf["discord-server"] %}
|
{% if conf["DISCORD_SERVER"] %}
|
||||||
<li class="nav-item right">
|
<li class="nav-item right">
|
||||||
<a class="nav-link" href="{{ conf['discord-server'] }}">Discord</a>
|
<a class="nav-link" href="{{ conf['DISCORD_SERVER'] }}">Discord</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -32,21 +32,21 @@
|
|||||||
<!-- right side -->
|
<!-- right side -->
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
|
|
||||||
{% if conf["instagram"] %}
|
{% if conf["INSTAGRAM"] %}
|
||||||
<li class="nav-item right">
|
<li class="nav-item right">
|
||||||
<a class="nav-link" href="{{ conf['instagram'] }}">Instagram</a>
|
<a class="nav-link" href="{{ conf['INSTAGRAM'] }}">Instagram</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if conf["facebook"] %}
|
{% if conf["FACEBOOK"] %}
|
||||||
<li class="nav-item right">
|
<li class="nav-item right">
|
||||||
<a class="nav-link" href="{{ conf['facebook'] }}">Facebook</a>
|
<a class="nav-link" href="{{ conf['FACEBOOK'] }}">Facebook</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if conf["twitter"] %}
|
{% if conf["TWITTER"] %}
|
||||||
<li>
|
<li>
|
||||||
<a class="nav-link" href="{{ conf['twitter'] }}">Twitter</a>
|
<a class="nav-link" href="{{ conf['TWITTER'] }}">Twitter</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col image-min-dimensions">
|
<div class="col image-min-dimensions">
|
||||||
<img class="img-responsive w-100 image-max-dimensions"
|
<img class="img-responsive w-100 image-max-dimensions"
|
||||||
src="/static/pictures/{{ p['image'] }}"></img>
|
src="/pictures/{{ p['image'] }}"></img>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
Reference in New Issue
Block a user