From edefdcb7430f40a8f2143b89f5a1474c2810d75a Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Tue, 25 Mar 2025 09:14:31 +0100 Subject: [PATCH 1/4] Remove trailing/ from URL --- changedetectionio/blueprint/backups/__init__.py | 2 +- changedetectionio/blueprint/rss/__init__.py | 3 +-- changedetectionio/blueprint/settings/__init__.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/changedetectionio/blueprint/backups/__init__.py b/changedetectionio/blueprint/backups/__init__.py index add44308c3e..fe793d1c4fe 100644 --- a/changedetectionio/blueprint/backups/__init__.py +++ b/changedetectionio/blueprint/backups/__init__.py @@ -138,7 +138,7 @@ def download_backup(filename): return send_from_directory(os.path.abspath(datastore.datastore_path), filename, as_attachment=True) @login_optionally_required - @backups_blueprint.route("/", methods=['GET']) + @backups_blueprint.route("", methods=['GET']) def index(): backups = find_backups() output = render_template("overview.html", diff --git a/changedetectionio/blueprint/rss/__init__.py b/changedetectionio/blueprint/rss/__init__.py index d9a87695c52..113e35ac5a9 100644 --- a/changedetectionio/blueprint/rss/__init__.py +++ b/changedetectionio/blueprint/rss/__init__.py @@ -13,8 +13,7 @@ def construct_blueprint(datastore: ChangeDetectionStore): # Import the login decorator if needed # from changedetectionio.auth_decorator import login_optionally_required - - @rss_blueprint.route("/", methods=['GET']) + @rss_blueprint.route("", methods=['GET']) def feed(): now = time.time() # Always requires token set diff --git a/changedetectionio/blueprint/settings/__init__.py b/changedetectionio/blueprint/settings/__init__.py index 5375b565cc0..2137487cd3c 100644 --- a/changedetectionio/blueprint/settings/__init__.py +++ b/changedetectionio/blueprint/settings/__init__.py @@ -13,7 +13,7 @@ def construct_blueprint(datastore: ChangeDetectionStore): settings_blueprint = Blueprint('settings', __name__, template_folder="templates") - @settings_blueprint.route("/", methods=['GET', "POST"]) + @settings_blueprint.route("", methods=['GET', "POST"]) @login_optionally_required def settings_page(): from changedetectionio import forms From 0131de3e3ad0dacb33859ac4de2b1c0edb91572c Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Tue, 25 Mar 2025 09:15:07 +0100 Subject: [PATCH 2/4] Moving watch-overview to its own blueprint --- .../blueprint/watchlist/__init__.py | 114 ++++++++++++++++++ .../watchlist}/templates/watch-overview.html | 20 +-- changedetectionio/flask_app.py | 104 ++-------------- changedetectionio/tests/util.py | 6 +- 4 files changed, 135 insertions(+), 109 deletions(-) create mode 100644 changedetectionio/blueprint/watchlist/__init__.py rename changedetectionio/{ => blueprint/watchlist}/templates/watch-overview.html (87%) diff --git a/changedetectionio/blueprint/watchlist/__init__.py b/changedetectionio/blueprint/watchlist/__init__.py new file mode 100644 index 00000000000..fb57b2ee427 --- /dev/null +++ b/changedetectionio/blueprint/watchlist/__init__.py @@ -0,0 +1,114 @@ +import flask_login +import os +import time +import timeago + +from flask import Blueprint, request, make_response, render_template, redirect, url_for, flash, session +from flask_login import current_user +from flask_paginate import Pagination, get_page_parameter + +from changedetectionio import forms +from changedetectionio.store import ChangeDetectionStore +from changedetectionio.auth_decorator import login_optionally_required +from changedetectionio.strtobool import strtobool + +def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMetaData): + watchlist_blueprint = Blueprint('watchlist', __name__, template_folder="templates") + + @watchlist_blueprint.route("/", methods=['GET']) + @login_optionally_required + def index(): + active_tag_req = request.args.get('tag', '').lower().strip() + active_tag_uuid = active_tag = None + + # Be sure limit_tag is a uuid + if active_tag_req: + for uuid, tag in datastore.data['settings']['application'].get('tags', {}).items(): + if active_tag_req == tag.get('title', '').lower().strip() or active_tag_req == uuid: + active_tag = tag + active_tag_uuid = uuid + break + + # Redirect for the old rss path which used the /?rss=true + if request.args.get('rss'): + return redirect(url_for('rss.feed', tag=active_tag_uuid)) + + op = request.args.get('op') + if op: + uuid = request.args.get('uuid') + if op == 'pause': + datastore.data['watching'][uuid].toggle_pause() + elif op == 'mute': + datastore.data['watching'][uuid].toggle_mute() + + datastore.needs_write = True + return redirect(url_for('watchlist.index', tag = active_tag_uuid)) + + # Sort by last_changed and add the uuid which is usually the key.. + sorted_watches = [] + with_errors = request.args.get('with_errors') == "1" + errored_count = 0 + search_q = request.args.get('q').strip().lower() if request.args.get('q') else False + for uuid, watch in datastore.data['watching'].items(): + if with_errors and not watch.get('last_error'): + continue + + if active_tag_uuid and not active_tag_uuid in watch['tags']: + continue + if watch.get('last_error'): + errored_count += 1 + + if search_q: + if (watch.get('title') and search_q in watch.get('title').lower()) or search_q in watch.get('url', '').lower(): + sorted_watches.append(watch) + elif watch.get('last_error') and search_q in watch.get('last_error').lower(): + sorted_watches.append(watch) + else: + sorted_watches.append(watch) + + form = forms.quickWatchForm(request.form) + page = request.args.get(get_page_parameter(), type=int, default=1) + total_count = len(sorted_watches) + + pagination = Pagination(page=page, + total=total_count, + per_page=datastore.data['settings']['application'].get('pager_size', 50), css_framework="semantic") + + sorted_tags = sorted(datastore.data['settings']['application'].get('tags').items(), key=lambda x: x[1]['title']) + output = render_template( + "watch-overview.html", + # Don't link to hosting when we're on the hosting environment + active_tag=active_tag, + active_tag_uuid=active_tag_uuid, + app_rss_token=datastore.data['settings']['application'].get('rss_access_token'), + datastore=datastore, + errored_count=errored_count, + form=form, + guid=datastore.data['app_guid'], + has_proxies=datastore.proxy_list, + has_unviewed=datastore.has_unviewed, + hosted_sticky=os.getenv("SALTED_PASS", False) == False, + pagination=pagination, + queued_uuids=[q_uuid.item['uuid'] for q_uuid in update_q.queue], + search_q=request.args.get('q','').strip(), + sort_attribute=request.args.get('sort') if request.args.get('sort') else request.cookies.get('sort'), + sort_order=request.args.get('order') if request.args.get('order') else request.cookies.get('order'), + system_default_fetcher=datastore.data['settings']['application'].get('fetch_backend'), + tags=sorted_tags, + watches=sorted_watches + ) + + if session.get('share-link'): + del(session['share-link']) + + resp = make_response(output) + + # The template can run on cookie or url query info + if request.args.get('sort'): + resp.set_cookie('sort', request.args.get('sort')) + if request.args.get('order'): + resp.set_cookie('order', request.args.get('order')) + + return resp + + return watchlist_blueprint \ No newline at end of file diff --git a/changedetectionio/templates/watch-overview.html b/changedetectionio/blueprint/watchlist/templates/watch-overview.html similarity index 87% rename from changedetectionio/templates/watch-overview.html rename to changedetectionio/blueprint/watchlist/templates/watch-overview.html index 0be142bab4c..2af9e61fde3 100644 --- a/changedetectionio/templates/watch-overview.html +++ b/changedetectionio/blueprint/watchlist/templates/watch-overview.html @@ -46,12 +46,12 @@ {% endif %} {% if search_q %}
Searching "{{search_q}}"
{% endif %}
- All + All {% for uuid, tag in tags %} {% if tag != "" %} - {{ tag.title }} + {{ tag.title }} {% endif %} {% endfor %}
@@ -72,14 +72,14 @@ {% set link_order = "desc" if sort_order == 'asc' else "asc" %} {% set arrow_span = "" %} - # + # - Website + Website {% if any_has_restock_price_processor %} Restock & Price {% endif %} - Last Checked - Last Changed + Last Checked + Last Changed @@ -104,12 +104,12 @@ {{ loop.index+pagination.skip }} {% if not watch.paused %} - Pause checks + Pause checks {% else %} - UnPause checks + UnPause checks {% endif %} {% set mute_label = 'UnMute notification' if watch.notification_muted else 'Mute notification' %} - {{ mute_label }} + {{ mute_label }} {{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}} @@ -210,7 +210,7 @@