Moved code and added env variables
This commit is contained in:
parent
6abf4a314f
commit
52f73d5bf9
@ -178,6 +178,14 @@
|
|||||||
# Default: https://rebrickable.com/parts/{number}/_/{color}
|
# Default: https://rebrickable.com/parts/{number}/_/{color}
|
||||||
# BK_REBRICKABLE_LINK_PART_PATTERN=
|
# BK_REBRICKABLE_LINK_PART_PATTERN=
|
||||||
|
|
||||||
|
# Optional: Pattern of the link to Rebrickable for instructions. Will be passed to Python .format()
|
||||||
|
# Default: https://rebrickable.com/instructions/{number}
|
||||||
|
# BK_REBRICKABLE_LINK_INSTRUCTIONS_PATTERN=
|
||||||
|
|
||||||
|
# Optional: User-Agent to use when querying Rebrickable outside of the Rebrick python library
|
||||||
|
# Default: 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||||
|
# BK_REBRICKABLE_USER_AGENT=
|
||||||
|
|
||||||
# Optional: Display Rebrickable links wherever applicable
|
# Optional: Display Rebrickable links wherever applicable
|
||||||
# Default: false
|
# Default: false
|
||||||
# Legacy name: LINKS
|
# Legacy name: LINKS
|
||||||
|
@ -43,6 +43,8 @@ CONFIG: Final[list[dict[str, Any]]] = [
|
|||||||
{'n': 'REBRICKABLE_IMAGE_NIL_MINIFIGURE', 'd': 'https://rebrickable.com/static/img/nil_mf.jpg'}, # noqa: E501
|
{'n': 'REBRICKABLE_IMAGE_NIL_MINIFIGURE', 'd': 'https://rebrickable.com/static/img/nil_mf.jpg'}, # noqa: E501
|
||||||
{'n': 'REBRICKABLE_LINK_MINIFIGURE_PATTERN', 'd': 'https://rebrickable.com/minifigs/{number}'}, # noqa: E501
|
{'n': 'REBRICKABLE_LINK_MINIFIGURE_PATTERN', 'd': 'https://rebrickable.com/minifigs/{number}'}, # noqa: E501
|
||||||
{'n': 'REBRICKABLE_LINK_PART_PATTERN', 'd': 'https://rebrickable.com/parts/{number}/_/{color}'}, # noqa: E501
|
{'n': 'REBRICKABLE_LINK_PART_PATTERN', 'd': 'https://rebrickable.com/parts/{number}/_/{color}'}, # noqa: E501
|
||||||
|
{'n': 'REBRICKABLE_LINK_INSTRUCTIONS_PATTERN', 'd': 'https://rebrickable.com/instructions/{number}'}, # noqa: E501
|
||||||
|
{'n': 'REBRICKABLE_USER_AGENT', 'd': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'},
|
||||||
{'n': 'REBRICKABLE_LINKS', 'e': 'LINKS', 'c': bool},
|
{'n': 'REBRICKABLE_LINKS', 'e': 'LINKS', 'c': bool},
|
||||||
{'n': 'REBRICKABLE_PAGE_SIZE', 'd': 100, 'c': int},
|
{'n': 'REBRICKABLE_PAGE_SIZE', 'd': 100, 'c': int},
|
||||||
{'n': 'RETIRED_SETS_FILE_URL', 'd': 'https://docs.google.com/spreadsheets/d/1rlYfEXtNKxUOZt2Mfv0H17DvK7bj6Pe0CuYwq6ay8WA/gviz/tq?tqx=out:csv&sheet=Sorted%20by%20Retirement%20Date'}, # noqa: E501
|
{'n': 'RETIRED_SETS_FILE_URL', 'd': 'https://docs.google.com/spreadsheets/d/1rlYfEXtNKxUOZt2Mfv0H17DvK7bj6Pe0CuYwq6ay8WA/gviz/tq?tqx=out:csv&sheet=Sorted%20by%20Retirement%20Date'}, # noqa: E501
|
||||||
|
@ -8,6 +8,7 @@ import humanize
|
|||||||
from werkzeug.datastructures import FileStorage
|
from werkzeug.datastructures import FileStorage
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
@ -117,21 +118,30 @@ class BrickInstructions(object):
|
|||||||
))
|
))
|
||||||
|
|
||||||
file.save(target)
|
file.save(target)
|
||||||
|
|
||||||
# Info
|
# Info
|
||||||
logger.info('The instruction file {file} has been imported'.format(
|
logger.info('The instruction file {file} has been imported'.format(
|
||||||
file=self.filename
|
file=self.filename
|
||||||
))
|
))
|
||||||
|
|
||||||
def find_instructions(self, set_id: str, /) -> None:
|
# Compute the url for the rebrickable instructions page
|
||||||
|
def url_for_instructions(self, /) -> str:
|
||||||
url = f"https://rebrickable.com/instructions/{set_id}"
|
try:
|
||||||
print(url)
|
return current_app.config['REBRICKABLE_LINK_INSTRUCTIONS_PATTERN'].format( # noqa: E501
|
||||||
|
number=self.filename,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def find_instructions(self, set: str, /) -> None:
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
'User-Agent': current_app.config['REBRICKABLE_USER_AGENT']
|
||||||
}
|
}
|
||||||
response = requests.get(url, headers=headers)
|
|
||||||
|
response = requests.get(BrickInstructions.url_for_instructions(self), headers=headers)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise ErrorException('Failed to load page. Status code: {response.status_code}')
|
raise ErrorException('Failed to load page. Status code: {response.status_code}')
|
||||||
|
|
||||||
@ -144,6 +154,7 @@ class BrickInstructions(object):
|
|||||||
img_tag = a_tag.find('img', alt=True)
|
img_tag = a_tag.find('img', alt=True)
|
||||||
if img_tag and "LEGO Building Instructions" in img_tag['alt']:
|
if img_tag and "LEGO Building Instructions" in img_tag['alt']:
|
||||||
found_tags.append((img_tag['alt'].replace('LEGO Building Instructions for ', ''), a_tag['href'])) # Save alt and href
|
found_tags.append((img_tag['alt'].replace('LEGO Building Instructions for ', ''), a_tag['href'])) # Save alt and href
|
||||||
|
|
||||||
return found_tags
|
return found_tags
|
||||||
|
|
||||||
def get_list(self, request_form, /) -> list:
|
def get_list(self, request_form, /) -> list:
|
||||||
@ -153,30 +164,28 @@ class BrickInstructions(object):
|
|||||||
if key.startswith('instruction-') and request_form.get(key) == 'on': # Checkbox is checked
|
if key.startswith('instruction-') and request_form.get(key) == 'on': # Checkbox is checked
|
||||||
index = key.split('-')[-1]
|
index = key.split('-')[-1]
|
||||||
alt_text = request_form.get(f'instruction-alt-text-{index}')
|
alt_text = request_form.get(f'instruction-alt-text-{index}')
|
||||||
href_text = request_form.get(f'instruction-href-text-{index}')
|
href_text = request_form.get(f'instruction-href-text-{index}').replace('/instructions/', '') # Remove the /instructions/ part
|
||||||
selected_instructions.append((href_text,alt_text))
|
selected_instructions.append((href_text,alt_text))
|
||||||
|
|
||||||
return selected_instructions
|
return selected_instructions
|
||||||
|
|
||||||
def download(self, href: str, /) -> None:
|
def download(self, href: str, /) -> None:
|
||||||
target = self.path(secure_filename(self.filename))
|
target = self.path(filename=secure_filename(self.filename))
|
||||||
|
|
||||||
if os.path.isfile(target):
|
if os.path.isfile(target):
|
||||||
raise ErrorException('Cannot download {target} as it already exists'.format( # noqa: E501
|
raise ErrorException('Cannot download {target} as it already exists'.format( # noqa: E501
|
||||||
target=self.filename
|
target=self.filename
|
||||||
))
|
))
|
||||||
|
|
||||||
url = f"https://rebrickable.com/{href}"
|
url = current_app.config['REBRICKABLE_LINK_INSTRUCTIONS_PATTERN'].format(number=href)
|
||||||
|
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# Save the file
|
# Save the content to the target path
|
||||||
with open(target, 'wb') as file:
|
FileStorage(stream=BytesIO(response.content)).save(target)
|
||||||
file.write(response.content)
|
|
||||||
else:
|
else:
|
||||||
raise ErrorException(f"Failed to download {self.filename}. Status code: {response.status_code}")
|
raise ErrorException(f"Failed to download {self.filename}. Status code: {response.status_code}")
|
||||||
|
|
||||||
|
|
||||||
# Info
|
# Info
|
||||||
logger.info('The instruction file {file} has been imported'.format(
|
logger.info('The instruction file {file} has been imported'.format(
|
||||||
file=self.filename
|
file=self.filename
|
||||||
|
@ -140,35 +140,35 @@ def download() -> str:
|
|||||||
error=request.args.get('error')
|
error=request.args.get('error')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Actually download an instructions file
|
# Show search results
|
||||||
@instructions_page.route('/download/', methods=['POST'])
|
@instructions_page.route('/download/', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@exception_handler(__file__, post_redirect='instructions.download')
|
@exception_handler(__file__, post_redirect='instructions.download')
|
||||||
def do_download() -> Response:
|
def do_download() -> Response:
|
||||||
|
# get set_id from input field
|
||||||
set_id: str = request.form.get('add-set', '')
|
set_id: str = request.form.get('add-set', '')
|
||||||
|
|
||||||
# BrickInstructions require an argument. Not sure which makes sense here.
|
# get list of instructions for the set and offer them to download
|
||||||
found_tags = BrickInstructions(set_id).find_instructions(set_id)
|
instructions = BrickInstructions(set_id).find_instructions(set_id)
|
||||||
|
|
||||||
return render_template('instructions.html', download=True, found_tags=found_tags)
|
return render_template('instructions.html', download=True, instructions=instructions)
|
||||||
|
|
||||||
@instructions_page.route('/confirm_download', methods=['POST'])
|
@instructions_page.route('/confirm_download', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@exception_handler(__file__, post_redirect='instructions.download')
|
@exception_handler(__file__, post_redirect='instructions.download')
|
||||||
def confirm_download() -> Response:
|
def confirm_download() -> Response:
|
||||||
|
|
||||||
# BrickInstructions require an argument. Not sure which makes sense here.
|
# Get list of selected instructions
|
||||||
selected_instructions = BrickInstructions("").get_list(request.form)
|
selected_instructions = BrickInstructions("").get_list(request.form)
|
||||||
|
|
||||||
|
# No instructions selected
|
||||||
if not selected_instructions:
|
if not selected_instructions:
|
||||||
# No instructions selected
|
|
||||||
return redirect(url_for('instructions.download'))
|
return redirect(url_for('instructions.download'))
|
||||||
|
|
||||||
|
# Loop over selected instructions and download them
|
||||||
for href, filename in selected_instructions:
|
for href, filename in selected_instructions:
|
||||||
BrickInstructions(f"{filename}.pdf").download(href)
|
BrickInstructions(f"{filename}.pdf").download(href)
|
||||||
|
|
||||||
|
|
||||||
BrickInstructionsList(force=True)
|
BrickInstructionsList(force=True)
|
||||||
|
|
||||||
#flash("Selected instructions have been downloaded", 'success')
|
|
||||||
return redirect(url_for('instructions.list'))
|
return redirect(url_for('instructions.list'))
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% if found_tags %}
|
{% if instructions %}
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="mb-0"><i class="ri-add-circle-line"></i> Select instructions to download</h5>
|
<h5 class="mb-0"><i class="ri-add-circle-line"></i> Select instructions to download</h5>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Available Instructions</label>
|
<label class="form-label">Available Instructions</label>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
{% for alt_text, href in found_tags %}
|
{% for alt_text, href in instructions %}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" name="instruction-{{ loop.index }}" id="instruction-{{ loop.index }}">
|
<input class="form-check-input" type="checkbox" name="instruction-{{ loop.index }}" id="instruction-{{ loop.index }}">
|
||||||
<label class="form-check-label" for="instruction-{{ loop.index }}">
|
<label class="form-check-label" for="instruction-{{ loop.index }}">
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
<span class="list-group-item list-group-item-action text-center"><i class="ri-error-warning-line"></i> No instructions file found.</span>
|
<span class="list-group-item list-group-item-action text-center"><i class="ri-error-warning-line"></i> No instructions file found.</span>
|
||||||
{% if g.login.is_authenticated() %}
|
{% if g.login.is_authenticated() %}
|
||||||
<a class="list-group-item list-group-item-action" href="{{ url_for('instructions.upload') }}"><i class="ri-upload-line"></i> Upload an instructions file</a>
|
<a class="list-group-item list-group-item-action" href="{{ url_for('instructions.upload') }}"><i class="ri-upload-line"></i> Upload an instructions file</a>
|
||||||
<a class="list-group-item list-group-item-action" href="{{ url_for('instructions.download', set_num=item.fields.set_num) }}"><i class="ri-download-line"></i> Download instruction from Rebrickable</a>
|
<a class="list-group-item list-group-item-action" href="{{ url_for('instructions.download', set_num=item.fields.number) }}"><i class="ri-download-line"></i> Download instruction from Rebrickable</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user