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

+ +
+
+ Calendar view + +
+ +
    + {% set view_choices = ["month"] %} + {% for vch in view_choices %} +
  • + {% if vch == viewas %} + {{ T(vch) }} + {% else %} + {{ T(vch) }} + {% endif %} +
  • + {% endfor %} +
+
+ +
+ +
+
+
+{% 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() }} ·