Skip to content

Path blueprint fixes and moving code blueprint #3054

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion changedetectionio/blueprint/backups/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion changedetectionio/blueprint/imports/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def import_page():
update_q.put(queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid}))

if len(importer_handler.remaining_data) == 0:
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))
else:
remaining_urls = importer_handler.remaining_data

Expand Down
4 changes: 2 additions & 2 deletions changedetectionio/blueprint/price_data_follower/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ def accept(uuid):
datastore.data['watching'][uuid]['processor'] = 'restock_diff'
datastore.data['watching'][uuid].clear_watch()
update_q.put(queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid}))
return redirect(url_for("index"))
return redirect(url_for("watchlist.index"))

@login_required
@price_data_follower_blueprint.route("/<string:uuid>/reject", methods=['GET'])
def reject(uuid):
datastore.data['watching'][uuid]['track_ldjson_price_data'] = PRICE_DATA_TRACK_REJECT
return redirect(url_for("index"))
return redirect(url_for("watchlist.index"))


return price_data_follower_blueprint
Expand Down
3 changes: 1 addition & 2 deletions changedetectionio/blueprint/rss/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions changedetectionio/blueprint/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -74,7 +74,7 @@ def settings_page():
datastore.needs_write_urgent = True
flash("Password protection enabled.", 'notice')
flask_login.logout_user()
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

datastore.needs_write_urgent = True
flash("Settings updated.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ <h4>Chrome Extension</h4>
<div id="actions">
<div class="pure-control-group">
{{ render_button(form.save_button) }}
<a href="{{url_for('index')}}" class="pure-button button-small button-cancel">Back</a>
<a href="{{url_for('watchlist.index')}}" class="pure-button button-small button-cancel">Back</a>
<a href="{{url_for('ui.clear_all_history')}}" class="pure-button button-small button-error">Clear Snapshot History</a>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<a class="link-mute state-{{'on' if tag.notification_muted else 'off'}}" href="{{url_for('tags.mute', uuid=tag.uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="Mute notifications" title="Mute notifications" class="icon icon-mute" ></a>
</td>
<td>{{ "{:,}".format(tag_count[uuid]) if uuid in tag_count else 0 }}</td>
<td class="title-col inline"> <a href="{{url_for('index', tag=uuid) }}">{{ tag.title }}</a></td>
<td class="title-col inline"> <a href="{{url_for('watchlist.index', tag=uuid) }}">{{ tag.title }}</a></td>
<td>
<a class="pure-button pure-button-primary" href="{{ url_for('tags.form_tag_edit', uuid=uuid) }}">Edit</a>&nbsp;
<a class="pure-button pure-button-primary" href="{{ url_for('tags.delete', uuid=uuid) }}" title="Deletes and removes tag">Delete</a>
Expand Down
18 changes: 9 additions & 9 deletions changedetectionio/blueprint/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def clear_watch_history(uuid):
else:
flash("Cleared snapshot history for watch {}".format(uuid))

return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

@ui_blueprint.route("/clear_history", methods=['GET', 'POST'])
@login_optionally_required
Expand All @@ -52,7 +52,7 @@ def clear_all_history():
else:
flash('Incorrect confirmation text.', 'error')

return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

output = render_template("clear_all_history.html")
return output
Expand All @@ -68,7 +68,7 @@ def mark_all_viewed():
continue
datastore.set_last_viewed(watch_uuid, int(time.time()))

return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

@ui_blueprint.route("/delete", methods=['GET'])
@login_optionally_required
Expand All @@ -77,15 +77,15 @@ def form_delete():

if uuid != 'all' and not uuid in datastore.data['watching'].keys():
flash('The watch by UUID {} does not exist.'.format(uuid), 'error')
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

# More for testing, possible to return the first/only
if uuid == 'first':
uuid = list(datastore.data['watching'].keys()).pop()
datastore.delete(uuid)
flash('Deleted.')

return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

@ui_blueprint.route("/clone", methods=['GET'])
@login_optionally_required
Expand All @@ -101,7 +101,7 @@ def form_clone():
update_q.put(queuedWatchMetaData.PrioritizedItem(priority=5, item={'uuid': new_uuid}))
flash('Cloned.')

return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

@ui_blueprint.route("/checknow", methods=['GET'])
@login_optionally_required
Expand Down Expand Up @@ -143,7 +143,7 @@ def form_watch_checknow():
if i == 0:
flash("No watches available to recheck.")

return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

@ui_blueprint.route("/form/checkbox-operations", methods=['POST'])
@login_optionally_required
Expand Down Expand Up @@ -244,7 +244,7 @@ def form_watch_list_checkbox_operations():

flash(f"{len(uuids)} watches were tagged")

return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))


@ui_blueprint.route("/share-url/<string:uuid>", methods=['GET'])
Expand Down Expand Up @@ -296,6 +296,6 @@ def form_share_put_watch(uuid):
logger.error(f"Error sharing -{str(e)}")
flash(f"Could not share, something went wrong while communicating with the share server - {str(e)}", 'error')

return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

return ui_blueprint
8 changes: 4 additions & 4 deletions changedetectionio/blueprint/ui/edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ def edit_page(uuid):
# More for testing, possible to return the first/only
if not datastore.data['watching'].keys():
flash("No watches to edit", "error")
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

if uuid == 'first':
uuid = list(datastore.data['watching'].keys()).pop()

if not uuid in datastore.data['watching']:
flash("No watch with the UUID %s found." % (uuid), "error")
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

switch_processor = request.args.get('switch_processor')
if switch_processor:
Expand All @@ -66,7 +66,7 @@ def edit_page(uuid):
processor_classes = next((tpl for tpl in processors.find_processors() if tpl[1] == processor_name), None)
if not processor_classes:
flash(f"Cannot load the edit form for processor/plugin '{processor_classes[1]}', plugin missing?", 'error')
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

parent_module = processors.get_parent_module(processor_classes[0])

Expand Down Expand Up @@ -207,7 +207,7 @@ def edit_page(uuid):
if request.args.get("next") and request.args.get("next") == 'diff':
return redirect(url_for('ui.ui_views.diff_history_page', uuid=uuid))

return redirect(url_for('index', tag=request.args.get("tag",'')))
return redirect(url_for('watchlist.index', tag=request.args.get("tag",'')))

else:
if request.method == 'POST' and not form.validate():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</div>
<br />
<div class="pure-control-group">
<a href="{{url_for('index')}}" class="pure-button button-cancel"
<a href="{{url_for('watchlist.index')}}" class="pure-button button-cancel"
>Cancel</a
>
</div>
Expand Down
10 changes: 5 additions & 5 deletions changedetectionio/blueprint/ui/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def preview_page(uuid):
watch = datastore.data['watching'][uuid]
except KeyError:
flash("No history found for the specified link, bad link?", "error")
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

system_uses_webdriver = datastore.data['settings']['application']['fetch_backend'] == 'html_webdriver'
extra_stylesheets = [url_for('static_content', group='styles', filename='diff.css')]
Expand Down Expand Up @@ -91,7 +91,7 @@ def diff_history_page(uuid):
watch = datastore.data['watching'][uuid]
except KeyError:
flash("No history found for the specified link, bad link?", "error")
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

# For submission of requesting an extract
extract_form = forms.extractDataForm(request.form)
Expand Down Expand Up @@ -119,7 +119,7 @@ def diff_history_page(uuid):

if len(dates) < 2:
flash("Not enough saved change detection snapshots to produce a report.", "error")
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

# Save the current newest history as the most recently viewed
datastore.set_last_viewed(uuid, time.time())
Expand Down Expand Up @@ -196,7 +196,7 @@ def form_quick_watch_add():
if not form.validate():
for widget, l in form.errors.items():
flash(','.join(l), 'error')
return redirect(url_for('index'))
return redirect(url_for('watchlist.index'))

url = request.form.get('url').strip()
if datastore.url_exists(url):
Expand All @@ -215,6 +215,6 @@ def form_quick_watch_add():
update_q.put(queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': new_uuid}))
flash("Watch added.")

return redirect(url_for('index', tag=request.args.get('tag','')))
return redirect(url_for('watchlist.index', tag=request.args.get('tag','')))

return views_blueprint
114 changes: 114 additions & 0 deletions changedetectionio/blueprint/watchlist/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Loading