Skip to content
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

Path blueprint fixes and moving code blueprint #3054

Merged
merged 4 commits into from
Mar 25, 2025
Merged
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
@@ -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",
2 changes: 1 addition & 1 deletion changedetectionio/blueprint/imports/__init__.py
Original file line number Diff line number Diff line change
@@ -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

4 changes: 2 additions & 2 deletions changedetectionio/blueprint/price_data_follower/__init__.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 1 addition & 2 deletions changedetectionio/blueprint/rss/__init__.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions changedetectionio/blueprint/settings/__init__.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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.")
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
18 changes: 9 additions & 9 deletions changedetectionio/blueprint/ui/__init__.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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'])
@@ -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
@@ -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:
@@ -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])

@@ -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():
Original file line number Diff line number Diff line change
@@ -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>
10 changes: 5 additions & 5 deletions changedetectionio/blueprint/ui/views.py
Original file line number Diff line number Diff line change
@@ -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')]
@@ -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)
@@ -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())
@@ -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):
@@ -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