From 6f53cd38368b3dfd0a5b8627844863132844768d Mon Sep 17 00:00:00 2001
From: Mattia Succurro
Date: Fri, 10 Feb 2023 14:15:21 +0100
Subject: [PATCH] Implemented calendar
---
CHANGELOG.md | 7 +-
app.py | 33 ++++--
extensions/instagram.py | 173 --------------------------------
i18n/salvi.en.json | 1 +
i18n/salvi.it.json | 1 +
static/style.css | 5 +
templates/calendar.html | 33 ++++++
templates/edit.html | 5 +
templates/home.html | 6 ++
templates/includes/nl_item.html | 6 ++
templates/listrecent.html | 13 ++-
templates/listtag.html | 6 ++
templates/month.html | 32 ++++++
templates/search.html | 2 +
templates/view.html | 4 +
15 files changed, 145 insertions(+), 182 deletions(-)
delete mode 100644 extensions/instagram.py
create mode 100644 templates/calendar.html
create mode 100644 templates/month.html
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f6b301..17b4aa3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,12 @@
+ Added `User` table.
+ Added `Flask-Login` and `Flask-WTF` dependencies in order to implement user logins.
+ Added `python-i18n` as a dependency. Therefore, i18n changed format, using JSON files now.
-+ Now you can export pages in a JSON format. Coming soon: importing.
++ Login is now required for creating and editing.
++ Now you can leave a comment while changing a page’s text. Moreover, a new revision is created now
+ only in case of an effective text change.
++ Now a page can be dated in the calendar.
++ Now you can export and import pages in a JSON format. Importing can be done by admin users only.
++ Improved page history view, and added user contributions page.
+ Like it or not, now gzip library is required.
+ Added CSS variables in the site style.
diff --git a/app.py b/app.py
index 72c3efb..9460d11 100644
--- a/app.py
+++ b/app.py
@@ -21,7 +21,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.routing import BaseConverter
from peewee import *
from playhouse.db_url import connect as dbconnect
-import datetime, hashlib, html, importlib, json, markdown, os, random, \
+import calendar, datetime, hashlib, html, importlib, json, markdown, os, random, \
re, sys, warnings
from functools import lru_cache, partial
from urllib.parse import quote
@@ -436,10 +436,10 @@ def is_url_available(url):
return url not in forbidden_urls and not Page.select().where(Page.url == url).exists()
forbidden_urls = [
- 'create', 'edit', 'p', 'ajax', 'history', 'manage', 'static', 'media',
- 'accounts', 'tags', 'init-config', 'upload', 'upload-info', 'about',
- 'stats', 'terms', 'privacy', 'easter', 'search', 'help', 'circles',
- 'protect', 'kt', 'embed', 'backlinks', 'u'
+ 'about', 'accounts', 'ajax', 'backlinks', 'calendar', 'circles', 'create',
+ 'easter', 'edit', 'embed', 'help', 'history', 'init-config', 'kt',
+ 'manage', 'media', 'p', 'privacy', 'protect', 'search', 'static', 'stats',
+ 'tags', 'terms', 'u', 'upload', 'upload-info'
]
app = Flask(__name__)
@@ -547,6 +547,7 @@ def savepoint(form, is_preview=False, pageobj=None):
pl_owner_is_current_user=pageobj.is_owned_by(current_user) if pageobj else True,
preview=preview,
pl_js_info=pl_js_info,
+ pl_calendar=('usecalendar' in form) and form['calendar'],
pl_readonly=not pageobj.can_edit(current_user) if pageobj else False
)
@@ -575,7 +576,8 @@ def create():
title=request.form['title'],
is_redirect=False,
touched=datetime.datetime.now(),
- owner=current_user,
+ owner_id=current_user.id,
+ calendar=datetime.date.fromisoformat(request.form["calendar"]) if 'usecalendar' in request.form else None,
is_math_enabled='enablemath' in request.form,
is_locked = 'lockpage' in request.form
)
@@ -632,6 +634,7 @@ def edit(id):
p.touched = datetime.datetime.now()
p.is_math_enabled = 'enablemath' in request.form
p.is_locked = 'lockpage' in request.form
+ p.calendar = datetime.date.fromisoformat(request.form["calendar"]) if 'usecalendar' in request.form else None
p.save()
p.change_tags(p_tags)
if request.form['text'] != p.latest.text:
@@ -657,6 +660,12 @@ def edit(id):
form["enablemath"] = "1"
if p.is_locked:
form["lockpage"] = "1"
+ if p.calendar:
+ form["usecalendar"] = "1"
+ try:
+ form["calendar"] = p.calendar.isoformat().split("T")[0]
+ except Exception:
+ form["calendar"] = p.calendar
return savepoint(form, pageobj=p)
@@ -778,6 +787,18 @@ def contributions(username):
abort(404)
return render_template('contributions.html', u=user, contributions=user.contributions.order_by(PageRevision.pub_date.desc()))
+@app.route('/calendar/')
+def calendar_view():
+ return render_template('calendar.html')
+
+@app.route('/calendar//')
+def calendar_month(y, m):
+ notes = Page.select().where(
+ (datetime.date(y, m, 1) <= Page.calendar) &
+ (Page.calendar < datetime.date(y+1 if m==12 else y, 1 if m==12 else m+1, 1))
+ ).order_by(Page.calendar)
+
+ return render_template('month.html', d=datetime.date(y, m, 1), notes=notes)
@app.route('/history/revision//')
def view_old(revisionid):
diff --git a/extensions/instagram.py b/extensions/instagram.py
deleted file mode 100644
index 431a239..0000000
--- a/extensions/instagram.py
+++ /dev/null
@@ -1,173 +0,0 @@
-from flask import Blueprint, render_template
-from peewee import *
-import instagram_private_api, json, os, sys, random, codecs
-
-database = SqliteDatabase('instagram.sqlite')
-
-class BaseModel(Model):
- class Meta:
- database = database
-
-class InstagramProfile(BaseModel):
- p_id = IntegerField()
- p_username = CharField(30)
- p_full_name = CharField(30)
- p_biography = CharField(150)
- posts_count = IntegerField()
- followers_count = IntegerField()
- following_count = IntegerField()
- flags = BitField()
- pub_date = DateTimeField()
- is_verified = flags.flag(1)
- is_private = flags.flag(2)
-
-class InstagramMedia(BaseModel):
- user = IntegerField()
- pub_date = DateTimeField()
- media_url = TextField()
- description = CharField(2200)
-
-def init_db():
- database.create_tables([InstagramProfile, InstagramMedia])
-
-def bytes_to_json(python_object):
- if isinstance(python_object, bytes):
- return {'__class__': 'bytes',
- '__value__': codecs.encode(python_object, 'base64').decode()}
- raise TypeError(repr(python_object) + ' is not JSON serializable')
-
-def bytes_from_json(json_object):
- if '__class__' in json_object and json_object['__class__'] == 'bytes':
- return codecs.decode(json_object['__value__'].encode(), 'base64')
- return json_object
-
-SETTINGS_PATH = 'ig_api_settings'
-
-def load_settings(username):
- with open(os.path.join(SETTINGS_PATH, username + '.json')) as f:
- settings = json.load(f, object_hook=bytes_from_json)
- return settings
-
-def save_settings(username, settings):
- with open(os.path.join(SETTINGS_PATH, username + '.json'), 'w') as f:
- json.dump(settings, f, default=bytes_to_json)
-
-CLIENTS = []
-
-def load_clients():
- try:
- with open(os.path.join(SETTINGS_PATH, 'config.txt')) as f:
- conf = f.read()
- except OSError:
- print('Config file not found.')
- return
- for up in conf.split('\n'):
- try:
- up = up.split('#')[0].strip()
- if not up:
- continue
- username, password = up.split(':')
- try:
- settings = load_settings(username)
- except Exception:
- settings = None
- try:
- if settings:
- device_id = settings.get('device_id')
- api = instagram_private_api.Client(
- username, password,
- settings=settings
- )
- else:
- api = instagram_private_api.Client(
- username, password,
- on_login=lambda x: save_settings(username, x.settings)
- )
- except (instagram_private_api.ClientCookieExpiredError,
- instagram_private_api.ClientLoginRequiredError) as e:
- api = instagram_private_api.Client(
- username, password,
- device_id=device_id,
- on_login=lambda x: save_settings(username, x.settings)
- )
- CLIENTS.append(api)
- except Exception:
- sys.excepthook(*sys.exc_info())
- continue
-
-def make_request(method_name, *args, **kwargs):
- exc = None
- usable_clients = list(range(len(CLIENTS)))
- while usable_clients:
- ci = random.choice(usable_clients)
- client = CLIENTS[ci]
- usable_clients.remove(ci)
- try:
- method = getattr(client, method_name)
- except AttributeError:
- raise ValueError('client has no method called {!r}'.format(method_name))
- if not callable(method):
- raise ValueError('client has no method called {!r}'.format(method_name))
- try:
- return method(*args, **kwargs)
- except Exception as e:
- exc = e
- if exc:
- raise exc
- else:
- raise RuntimeError('no active clients')
-
-N_FORCE = 0
-N_FALLBACK_CACHE = 1
-N_PREFER_CACHE = 2
-N_OFFLINE = 3
-
-def choose_method(online, offline, network):
- if network == N_FORCE:
- return online()
- elif network == N_FALLBACK_CACHE:
- try:
- return online()
- except Exception:
- return offline()
- elif network == N_PREFER_CACHE:
- try:
- return offline()
- except Exception:
- return online()
- elif network == N_OFFLINE:
- return offline()
-
-def get_profile_info(username_or_id, network=N_FALLBACK_CACHE):
- if isinstance(username_or_id, str):
- username, userid = username_or_id, None
- elif isinstance(username_or_id, int):
- username, userid = None, username_or_id
- else:
- raise TypeError('invalid username or id')
- def online():
- if userid:
- data = make_request('user_info', userid)
- else:
- data = make_request('username_info', username)
- return InstagramProfile.create(
- p_id = data['user']['pk'],
- p_username = data['user']['username'],
- p_full_name = data['user']['full_name'],
- p_biography = data['user']['biography'],
- posts_count = data['user']['media_count'],
- followers_count = data['user']['follower_count'],
- following_count = data['user']['following_count'],
- is_verified = data['user']['is_verified'],
- is_private = data['user']['is_private'],
- pub_date = datetime.datetime.now()
- )
- def offline():
- if userid:
- q = InstagramProfile.select().where(InstagramProfile.p_id == userid)
- else:
- q = InstagramProfile.select().where(InstagramProfile.p_username == username)
- return q.order_by(InstagramProfile.pub_date.desc())[0]
- return choose_method(online, offline, network)
-
-load_clients()
diff --git a/i18n/salvi.en.json b/i18n/salvi.en.json
index 5fb2b7c..b49cb7a 100644
--- a/i18n/salvi.en.json
+++ b/i18n/salvi.en.json
@@ -23,6 +23,7 @@
"random-page": "Random page",
"search": "Search",
"year": "Year",
+ "month": "Month",
"calculate": "Calculate",
"show-all": "Show all",
"just-now": "just now",
diff --git a/i18n/salvi.it.json b/i18n/salvi.it.json
index e5b5783..a01ce65 100644
--- a/i18n/salvi.it.json
+++ b/i18n/salvi.it.json
@@ -23,6 +23,7 @@
"random-page": "Pagina casuale",
"search": "Cerca",
"year": "Anno",
+ "month": "Mese",
"calculate": "Calcola",
"show-all": "Mostra tutto",
"just-now": "poco fa",
diff --git a/static/style.css b/static/style.css
index ba6f187..49e1a57 100644
--- a/static/style.css
+++ b/static/style.css
@@ -70,8 +70,13 @@ input[type="submit"],input[type="reset"],input[type="button"],button{font-family
.page-tags .tag-count{color: var(--btn-success);font-size:smaller;font-weight:600}
.search-wrapper {display:flex;width:90%;margin:auto}
.search-wrapper > input {flex:1}
+.calendar-subtitle {text-align: center; margin-top: -1em}
.preview-subtitle {text-align: center; margin-top: -1em}
textarea {background-color: var(--bg-sharp); color: var(--fg-sharp)}
+ul.inline {margin:0; padding:0; display: inline}
+ul.inline > li {display: inline-block;}
+ul.inline > li::after {content: "·"}
+ul.inline > li:last-child::after {content: ""}
/* Circles extension */
.circles-red{color: #e14}
diff --git a/templates/calendar.html b/templates/calendar.html
new file mode 100644
index 0000000..f3e5c23
--- /dev/null
+++ b/templates/calendar.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+
+{% block title %}Calendar – {{ app_name }}{% endblock %}
+
+{% block content %}
+Calendar
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/edit.html b/templates/edit.html
index 791bef8..bceabbc 100644
--- a/templates/edit.html
+++ b/templates/edit.html
@@ -77,6 +77,11 @@
{% endif %}
+
+
+
+
+
{% endif %}
diff --git a/templates/home.html b/templates/home.html
index c0d4691..f776700 100644
--- a/templates/home.html
+++ b/templates/home.html
@@ -25,6 +25,12 @@
{% endfor %}
{% endif %}
+ {% if n.calendar %}
+
+ calendar_today
+
+
+ {% endif %}
{% endfor %}
{{ T('show-all') }}
diff --git a/templates/includes/nl_item.html b/templates/includes/nl_item.html
index a9f01c6..c898901 100644
--- a/templates/includes/nl_item.html
+++ b/templates/includes/nl_item.html
@@ -12,3 +12,9 @@
{% endif %}
{% endfor %}
+{% if n.calendar %}
+
+ calendar_today
+
+
+{% endif %}
diff --git a/templates/listrecent.html b/templates/listrecent.html
index f8b7188..01bb1ff 100644
--- a/templates/listrecent.html
+++ b/templates/listrecent.html
@@ -13,11 +13,20 @@
{{ n.title }}
{{ n.short_desc() }}
- Tags:
+ {% if n.tags %}
+
{{ T('tags') }}:
{% for tag in n.tags %}
- #{{ tag.name }}
+ {% set tn = tag.name %}
+ #{{ tn }}
{% endfor %}
+ {% endif %}
+ {% if n.calendar %}
+
+ calendar_today
+
+
+ {% endif %}
{% endfor %}
{% if page_n <= total_count // 20 %}
diff --git a/templates/listtag.html b/templates/listtag.html
index 5742b86..68ef780 100644
--- a/templates/listtag.html
+++ b/templates/listtag.html
@@ -25,6 +25,12 @@
#{{ tn }}
{% endif %}
{% endfor %}
+ {% if n.calendar %}
+
+ calendar_today
+
+
+ {% endif %}
{% endfor %}
diff --git a/templates/month.html b/templates/month.html
new file mode 100644
index 0000000..9211ade
--- /dev/null
+++ b/templates/month.html
@@ -0,0 +1,32 @@
+{% extends "base.html" %}
+
+{% block title %}{{ d.strftime("%B %Y") }} – {{ app_name }}{% endblock %}
+
+{% block content %}
+{{ d.strftime("%B %Y") }}
+
+
+ {% for n in notes %}
+ -
+ {{ n.title }}
+
{{ n.short_desc() }}
+ {% if n.tags %}
+ {{ T('tags') }}:
+ {% for tag in n.tags %}
+ {% set tn = tag.name %}
+ #{{ tn }}
+ {% endfor %}
+
+ {% endif %}
+ {% if n.calendar %}
+
+ calendar_today
+
+
+ {% endif %}
+
+ {% endfor %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/search.html b/templates/search.html
index c332983..df1aaea 100644
--- a/templates/search.html
+++ b/templates/search.html
@@ -27,6 +27,8 @@
{% elif q %}
{{ T('search-no-results') }} {{ q }}
+{% else %}
+Please note that search queries do not search for page text.
{% endif %}
{% endblock %}
diff --git a/templates/view.html b/templates/view.html
index 696ebc1..b66fb00 100644
--- a/templates/view.html
+++ b/templates/view.html
@@ -7,6 +7,10 @@
{% block content %}
{{ p.title }}
+
+ {% if p.calendar %}
+ calendar_today{{ p.calendar.strftime('%B %-d, %Y') }}
+ {% endif %}
{{ T('last-changed') }} {{ rev.human_pub_date() }} ·