mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-08 18:47:32 +00:00
Compare commits
3 Commits
better-mer
...
3337-extra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6572efecc | ||
|
|
011fa3540e | ||
|
|
c3c3671f8b |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
||||||
|
|
||||||
__version__ = '0.50.6'
|
__version__ = '0.50.7'
|
||||||
|
|
||||||
from changedetectionio.strtobool import strtobool
|
from changedetectionio.strtobool import strtobool
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
|
|||||||
@@ -93,12 +93,15 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
|||||||
return redirect(url_for('watchlist.index'))
|
return redirect(url_for('watchlist.index'))
|
||||||
|
|
||||||
# For submission of requesting an extract
|
# For submission of requesting an extract
|
||||||
extract_form = forms.extractDataForm(request.form)
|
extract_form = forms.extractDataForm(formdata=request.form,
|
||||||
|
data={'extract_regex': request.form.get('extract_regex', '')}
|
||||||
|
)
|
||||||
if not extract_form.validate():
|
if not extract_form.validate():
|
||||||
flash("An error occurred, please see below.", "error")
|
flash("An error occurred, please see below.", "error")
|
||||||
|
return _render_diff_template(uuid, extract_form)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
extract_regex = request.form.get('extract_regex').strip()
|
extract_regex = request.form.get('extract_regex', '').strip()
|
||||||
output = watch.extract_regex_from_all_history(extract_regex)
|
output = watch.extract_regex_from_all_history(extract_regex)
|
||||||
if output:
|
if output:
|
||||||
watch_dir = os.path.join(datastore.datastore_path, uuid)
|
watch_dir = os.path.join(datastore.datastore_path, uuid)
|
||||||
@@ -109,12 +112,11 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
|||||||
response.headers['Expires'] = "0"
|
response.headers['Expires'] = "0"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
flash('Nothing matches that RegEx', 'error')
|
flash('No matches found while scanning all of the watch history for that RegEx.', 'error')
|
||||||
redirect(url_for('ui_views.diff_history_page', uuid=uuid) + '#extract')
|
return redirect(url_for('ui.ui_views.diff_history_page', uuid=uuid) + '#extract')
|
||||||
|
|
||||||
@views_blueprint.route("/diff/<string:uuid>", methods=['GET'])
|
def _render_diff_template(uuid, extract_form=None):
|
||||||
@login_optionally_required
|
"""Helper function to render the diff template with all required data"""
|
||||||
def diff_history_page(uuid):
|
|
||||||
from changedetectionio import forms
|
from changedetectionio import forms
|
||||||
|
|
||||||
# More for testing, possible to return the first/only
|
# More for testing, possible to return the first/only
|
||||||
@@ -128,8 +130,11 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
|||||||
flash("No history found for the specified link, bad link?", "error")
|
flash("No history found for the specified link, bad link?", "error")
|
||||||
return redirect(url_for('watchlist.index'))
|
return redirect(url_for('watchlist.index'))
|
||||||
|
|
||||||
# For submission of requesting an extract
|
# Use provided form or create a new one
|
||||||
extract_form = forms.extractDataForm(request.form)
|
if extract_form is None:
|
||||||
|
extract_form = forms.extractDataForm(formdata=request.form,
|
||||||
|
data={'extract_regex': request.form.get('extract_regex', '')}
|
||||||
|
)
|
||||||
|
|
||||||
history = watch.history
|
history = watch.history
|
||||||
dates = list(history.keys())
|
dates = list(history.keys())
|
||||||
@@ -170,7 +175,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
|||||||
|
|
||||||
datastore.set_last_viewed(uuid, time.time())
|
datastore.set_last_viewed(uuid, time.time())
|
||||||
|
|
||||||
output = render_template("diff.html",
|
return render_template("diff.html",
|
||||||
current_diff_url=watch['url'],
|
current_diff_url=watch['url'],
|
||||||
from_version=str(from_version),
|
from_version=str(from_version),
|
||||||
to_version=str(to_version),
|
to_version=str(to_version),
|
||||||
@@ -193,7 +198,10 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
|||||||
watch_a=watch
|
watch_a=watch
|
||||||
)
|
)
|
||||||
|
|
||||||
return output
|
@views_blueprint.route("/diff/<string:uuid>", methods=['GET'])
|
||||||
|
@login_optionally_required
|
||||||
|
def diff_history_page(uuid):
|
||||||
|
return _render_diff_template(uuid)
|
||||||
|
|
||||||
@views_blueprint.route("/form/add/quickwatch", methods=['POST'])
|
@views_blueprint.route("/form/add/quickwatch", methods=['POST'])
|
||||||
@login_optionally_required
|
@login_optionally_required
|
||||||
|
|||||||
@@ -81,10 +81,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
{%- if any_has_restock_price_processor -%}
|
{%- if any_has_restock_price_processor -%}
|
||||||
{%- set cols_required = cols_required + 1 -%}
|
{%- set cols_required = cols_required + 1 -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
{%- set ui_settings = datastore.data['settings']['application']['ui'] -%}
|
||||||
|
|
||||||
<div id="watch-table-wrapper">
|
<div id="watch-table-wrapper">
|
||||||
{%- set table_classes = [
|
{%- set table_classes = [
|
||||||
'favicon-enabled' if datastore.data['settings']['application']['ui'].get('favicons_enabled') else 'favicon-not-enabled',
|
'favicon-enabled' if 'favicons_enabled' not in ui_settings or ui_settings['favicons_enabled'] else 'favicon-not-enabled',
|
||||||
] -%}
|
] -%}
|
||||||
<table class="pure-table pure-table-striped watch-table {{ table_classes | reject('equalto', '') | join(' ') }}">
|
<table class="pure-table pure-table-striped watch-table {{ table_classes | reject('equalto', '') | join(' ') }}">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -147,7 +148,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
<td class="title-col inline">
|
<td class="title-col inline">
|
||||||
<div class="flex-wrapper">
|
<div class="flex-wrapper">
|
||||||
{% if datastore.data['settings']['application']['ui'].get('favicons_enabled') %}
|
{% if 'favicons_enabled' not in ui_settings or ui_settings['favicons_enabled'] %}
|
||||||
<div>{# A page might have hundreds of these images, set IMG options for lazy loading, don't set SRC if we dont have it so it doesnt fetch the placeholder' #}
|
<div>{# A page might have hundreds of these images, set IMG options for lazy loading, don't set SRC if we dont have it so it doesnt fetch the placeholder' #}
|
||||||
<img alt="Favicon thumbnail" class="favicon" loading="lazy" decoding="async" fetchpriority="low" {% if favicon %} src="{{url_for('static_content', group='favicon', filename=watch.uuid)}}" {% else %} src='data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="7.087" height="7.087" viewBox="0 0 7.087 7.087"%3E%3Ccircle cx="3.543" cy="3.543" r="3.279" stroke="%23e1e1e1" stroke-width="0.45" fill="none" opacity="0.74"/%3E%3C/svg%3E' {% endif %} />
|
<img alt="Favicon thumbnail" class="favicon" loading="lazy" decoding="async" fetchpriority="low" {% if favicon %} src="{{url_for('static_content', group='favicon', filename=watch.uuid)}}" {% else %} src='data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="7.087" height="7.087" viewBox="0 0 7.087 7.087"%3E%3Ccircle cx="3.543" cy="3.543" r="3.279" stroke="%23e1e1e1" stroke-width="0.45" fill="none" opacity="0.74"/%3E%3C/svg%3E' {% endif %} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -396,6 +396,19 @@ def validate_url(test_url):
|
|||||||
# This should be wtforms.validators.
|
# This should be wtforms.validators.
|
||||||
raise ValidationError('Watch protocol is not permitted by SAFE_PROTOCOL_REGEX or incorrect URL format')
|
raise ValidationError('Watch protocol is not permitted by SAFE_PROTOCOL_REGEX or incorrect URL format')
|
||||||
|
|
||||||
|
|
||||||
|
class ValidateSinglePythonRegexString(object):
|
||||||
|
def __init__(self, message=None):
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def __call__(self, form, field):
|
||||||
|
try:
|
||||||
|
re.compile(field.data)
|
||||||
|
except re.error:
|
||||||
|
message = field.gettext('RegEx \'%s\' is not a valid regular expression.')
|
||||||
|
raise ValidationError(message % (field.data))
|
||||||
|
|
||||||
|
|
||||||
class ValidateListRegex(object):
|
class ValidateListRegex(object):
|
||||||
"""
|
"""
|
||||||
Validates that anything that looks like a regex passes as a regex
|
Validates that anything that looks like a regex passes as a regex
|
||||||
@@ -414,6 +427,7 @@ class ValidateListRegex(object):
|
|||||||
message = field.gettext('RegEx \'%s\' is not a valid regular expression.')
|
message = field.gettext('RegEx \'%s\' is not a valid regular expression.')
|
||||||
raise ValidationError(message % (line))
|
raise ValidationError(message % (line))
|
||||||
|
|
||||||
|
|
||||||
class ValidateCSSJSONXPATHInput(object):
|
class ValidateCSSJSONXPATHInput(object):
|
||||||
"""
|
"""
|
||||||
Filter validation
|
Filter validation
|
||||||
@@ -791,5 +805,5 @@ class globalSettingsForm(Form):
|
|||||||
|
|
||||||
|
|
||||||
class extractDataForm(Form):
|
class extractDataForm(Form):
|
||||||
extract_regex = StringField('RegEx to extract', validators=[validators.Length(min=1, message="Needs a RegEx")])
|
extract_regex = StringField('RegEx to extract', validators=[validators.DataRequired(), ValidateSinglePythonRegexString()])
|
||||||
extract_submit_button = SubmitField('Extract as CSV', render_kw={"class": "pure-button pure-button-primary"})
|
extract_submit_button = SubmitField('Extract as CSV', render_kw={"class": "pure-button pure-button-primary"})
|
||||||
|
|||||||
@@ -639,7 +639,7 @@ class model(watch_base):
|
|||||||
if res:
|
if res:
|
||||||
if not csv_writer:
|
if not csv_writer:
|
||||||
# A file on the disk can be transferred much faster via flask than a string reply
|
# A file on the disk can be transferred much faster via flask than a string reply
|
||||||
csv_output_filename = 'report.csv'
|
csv_output_filename = f"report-{self.get('uuid')}.csv"
|
||||||
f = open(os.path.join(self.watch_data_dir, csv_output_filename), 'w')
|
f = open(os.path.join(self.watch_data_dir, csv_output_filename), 'w')
|
||||||
# @todo some headers in the future
|
# @todo some headers in the future
|
||||||
#fieldnames = ['Epoch seconds', 'Date']
|
#fieldnames = ['Epoch seconds', 'Date']
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ def test_check_extract_text_from_diff(client, live_server, measure_memory_usage)
|
|||||||
follow_redirects=False
|
follow_redirects=False
|
||||||
)
|
)
|
||||||
|
|
||||||
assert b'Nothing matches that RegEx' not in res.data
|
assert b'No matches found while scanning all of the watch history for that RegEx.' not in res.data
|
||||||
assert res.content_type == 'text/csv'
|
assert res.content_type == 'text/csv'
|
||||||
|
|
||||||
# Read the csv reply as stringio
|
# Read the csv reply as stringio
|
||||||
|
|||||||
Reference in New Issue
Block a user