From cb66468c3ed2b7b3bfe5b77e3149d239129ea3f3 Mon Sep 17 00:00:00 2001 From: Pete Date: Mon, 21 Oct 2024 14:28:22 +0200 Subject: [PATCH 1/5] refactor: Remove GET method from /create route --- webapp/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/routes.py b/webapp/routes.py index 353e91c..ff1cad5 100644 --- a/webapp/routes.py +++ b/webapp/routes.py @@ -82,7 +82,7 @@ def home(): ) -@ui_blueprint.route("/create", methods=["GET", "POST"]) +@ui_blueprint.route("/create", methods=["POST"]) @login_required def create(): created_assets = [] From c17bfc68698617999d274ed3ce6745bffb0e2913 Mon Sep 17 00:00:00 2001 From: Pete Date: Wed, 23 Oct 2024 18:45:57 +0200 Subject: [PATCH 2/5] feat: Update design for asset cards --- static/sass/main.scss | 6 +++- templates/_asset-card-image.html | 51 ++++++++++++++++++++++++++++ templates/_asset-card.html | 48 ++++++++++++++++++++++++++ templates/_asset-list.html | 6 +++- templates/_asset.html | 57 ------------------------------- templates/created.html | 4 +-- templates/details.html | 58 ++++++++++++++++++++++++++++++++ templates/index.html | 2 +- templates/update.html | 2 +- webapp/routes.py | 12 ++++++- webapp/services.py | 34 ++++++++++++------- yarn.lock | 2 +- 12 files changed, 205 insertions(+), 77 deletions(-) create mode 100644 templates/_asset-card-image.html create mode 100644 templates/_asset-card.html delete mode 100644 templates/_asset.html create mode 100644 templates/details.html diff --git a/static/sass/main.scss b/static/sass/main.scss index ff3c0fb..86d6ee3 100644 --- a/static/sass/main.scss +++ b/static/sass/main.scss @@ -10,7 +10,7 @@ } } -.asset-thumbnail { +.p-asset-card--thumbnail { max-height: 100px; min-height: 100px; } @@ -40,4 +40,8 @@ .p-filter-panel-section__chips-toggle { padding-top: 6px; margin-bottom: 18px; +.p-asset-card { + padding:0.75rem; + background:#f7f7f7; + margin-top:2rem; } \ No newline at end of file diff --git a/templates/_asset-card-image.html b/templates/_asset-card-image.html new file mode 100644 index 0000000..b7a78c9 --- /dev/null +++ b/templates/_asset-card-image.html @@ -0,0 +1,51 @@ +
+
+ {% if asset.data.image %} + + + + {% endif %} +
+
+ {% if asset.name %} +

{{ asset.name }}

+ {% else %} +

{{ asset.file_path.split('.')[0] }}

+ {% endif %} +

File type: .{{ asset.file_path.split('.', 1)[1] }}

+

Image size: {% if asset.data.width and asset.data.height %}{{asset.data.width}} x {{asset.data.height}}{% endif %}

+

+ Tags: + {% for tag in asset.tags %} + {{ tag.name }}{% if not loop.last %},{% endif %} + {% endfor %} +

+

+ Date created: {{ asset.created.strftime('%d %B %Y') }} +

+
+ +
+

+ + + Edit asset + +

+
+
diff --git a/templates/_asset-card.html b/templates/_asset-card.html new file mode 100644 index 0000000..453df9c --- /dev/null +++ b/templates/_asset-card.html @@ -0,0 +1,48 @@ +
+
+ {% if asset.asset_type == "whitepaper" %} + + {% else %} + + {% endif %} +
+
+ {% if asset.name %} +

{{ asset.name }}

+ {% else %} +

{{ asset.file_path.split('.')[0] }}

+ {% endif %} +

File type: .{{ asset.file_path.split('.', 1)[1] }}

+

Asset type: {{ asset.asset_type }}

+

+ Tags: + {% for tag in asset.tags %} + {{ tag.name }}{% if not loop.last %},{% endif %} + {% endfor %} +

+

+ Date created: {{ asset.created.strftime('%d %B %Y') }} +

+
+ +
+

+ + + Edit asset + +

+
+
diff --git a/templates/_asset-list.html b/templates/_asset-list.html index 0f5974d..f561c60 100644 --- a/templates/_asset-list.html +++ b/templates/_asset-list.html @@ -1,3 +1,7 @@ {% for asset in assets %} - {% include "_asset.html" %} + {% if asset.data.image %} + {% include "_asset-card-image.html" %} + {% else %} + {% include "_asset-card.html" %} + {% endif %} {% endfor %} \ No newline at end of file diff --git a/templates/_asset.html b/templates/_asset.html deleted file mode 100644 index 4b1e1fa..0000000 --- a/templates/_asset.html +++ /dev/null @@ -1,57 +0,0 @@ -
- {% if asset.data.image %} - - - -
- {% endif %} -
-

{{ asset.file_path }}

-

- - Tags: {% for tag in asset.tags %} - - {{tag.name}} - - {% endfor %} - -

-

- Created: {{ asset.created.strftime('%d/%m/%Y, %H:%M') }} -

-

- Copy: - - {% if asset.data.image %} - - {% endif %} -

-

- - - Edit - - {% if asset.deprecated %} - - Deprecated - - {% endif %} -

-
-
diff --git a/templates/created.html b/templates/created.html index 07c2b22..0ec2442 100644 --- a/templates/created.html +++ b/templates/created.html @@ -13,14 +13,14 @@

Failed to upload {{failed|length}} assets

{% endif %} {% if assets %} -
+

{{assets|length}} assets created

{% include "_asset-list.html" %}
{% endif %} {% if existing %} -
+
{% set assets = existing %}

{{existing|length}} existing assets

{% include "_asset-list.html" %} diff --git a/templates/details.html b/templates/details.html new file mode 100644 index 0000000..35742ac --- /dev/null +++ b/templates/details.html @@ -0,0 +1,58 @@ +{% extends "_layout.html" %} + +{% block title %}Asset details{% endblock %} + +{% block content %} +
+
+
+

Asset details

+ {% if asset.data.image %} + + + + {% endif %} +

File path:{{ file_path }}

+

Name: {{ asset.name }}

+

File type: .{{ asset.file_path.split('.', 1)[1] }}

+

Asset type: {{ asset.asset_type }}

+

Image size: {% if asset.data.width and asset.data.height %}{{asset.data.width}} x {{asset.data.height}}{% else %}Unknown{% endif %}

+

+ Products: + {% for product in asset.products %} + {{ product.name }}{% if not loop.last %},{% endif %} + {% endfor %} +

+

+ Tags: + {% for tag in asset.tags %} + {{ tag.name }}{% if not loop.last %},{% endif %} + {% endfor %} +

+

Created: {{ asset.created.strftime('%d %B %Y') }}

+

Updated: {{ asset.updated.strftime('%d %B %Y') }}

+

Google drive link: {{ asset.google_drive_link }}

+

Author email: {{ asset.author_email }}

+

Language: {{ asset.language }}

+

Salesforce campaign ID: {{ asset.salesforce_campaign_id }}

+

Deprecated: {{ asset.deprecated }}

+

+ + +

+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 9db02a0..d353c41 100644 --- a/templates/index.html +++ b/templates/index.html @@ -34,7 +34,7 @@
-
+
{% if assets %} {% include "_asset-list.html" %} {% elif request.values.get("q", None) %} diff --git a/templates/update.html b/templates/update.html index 9c7561a..fe7c1ce 100644 --- a/templates/update.html +++ b/templates/update.html @@ -24,7 +24,7 @@

{{ asset.file_path or request.args.get("file-path")}}

{% if asset.data.image %} {% endif %} diff --git a/webapp/routes.py b/webapp/routes.py index ff1cad5..7b7fe21 100644 --- a/webapp/routes.py +++ b/webapp/routes.py @@ -82,7 +82,7 @@ def home(): ) -@ui_blueprint.route("/create", methods=["POST"]) +@ui_blueprint.route("/create", methods=["GET", "POST"]) @login_required def create(): created_assets = [] @@ -205,6 +205,16 @@ def update(): "create-update.html", products_list=products_list, asset=asset ) +@ui_blueprint.route("/details", methods=["GET"]) +@login_required +def details(): + file_path = request.args.get("file-path") + + asset = asset_service.find_asset(file_path) + if not asset: + flask.flash("Asset not found", "negative") + + return flask.render_template("details.html", asset=asset) # API Routes # === diff --git a/webapp/services.py b/webapp/services.py index 9f11e2f..4a6fdda 100644 --- a/webapp/services.py +++ b/webapp/services.py @@ -1,11 +1,13 @@ # System import imghdr -from base64 import b64decode +from base64 import b64decode, b64encode from datetime import datetime, timezone +from io import BytesIO # Packages from wand.image import Image from typing import List +from PIL import Image as PillowImage # Local from webapp.database import db_session @@ -98,7 +100,11 @@ def create_asset( friendly_name = clean_unicode(friendly_name) url_path = clean_unicode(url_path) - encoded_file_content = (b64decode(file_content),) + # First we ensure it is b64 encoded + encoded_file_content = b64encode(file_content) + # Then we can decode it + decoded_file_content = (b64decode(encoded_file_content)) + if imghdr.what(None, h=file_content) is not None or is_svg( file_content ): @@ -107,29 +113,33 @@ def create_asset( # As it's not an image, there is no need for optimization data["optimized"] = False + if data.get("image"): + try: + # Use Pillow to open the image and get dimensions + with PillowImage.open(BytesIO(decoded_file_content)) as img: + data["width"] = img.width + data["height"] = img.height + except Exception as e: + print(f"Error opening image with Pillow: {e}") + data["width"] = None + data["height"] = None + # Try to optimize the asset if it's an image if data.get("image") and optimize: try: - image = ImageProcessor(encoded_file_content) + image = ImageProcessor(decoded_file_content) image.optimize(allow_svg_errors=True) - encoded_file_content = image.data + decoded_file_content = image.data data["optimized"] = True except Exception: # If optimisation failed, just don't bother optimising data["optimized"] = False + if not url_path: url_path = file_manager.generate_asset_path( file_content, friendly_name ) - if data.get("image"): - try: - with Image(blob=encoded_file_content) as image_info: - data["width"] = image_info.width - data["height"] = image_info.height - except Exception: - # Just don't worry if image reading fails - pass asset = ( db_session.query(Asset) .filter(Asset.file_path == url_path) diff --git a/yarn.lock b/yarn.lock index b914ccf..4dee3ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1654,7 +1654,7 @@ util@^0.10.3: vanilla-framework@4.16.0: version "4.16.0" - resolved "https://registry.npmjs.org/vanilla-framework/-/vanilla-framework-4.16.0.tgz" + resolved "https://registry.yarnpkg.com/vanilla-framework/-/vanilla-framework-4.16.0.tgz#54e7a51e073de043d45a7bac37ffaa4f4351ac4a" integrity sha512-LrxZLiNOm0cTpG++1X4MMy2efg8Xhc08JUAwNAybeSQ5FaaBGiAodbV1Fx3QvJxlaPFQqsjOdT6uZDwuOD7YJg== verbalize@^0.1.2: From 2ff4f8db74f150d9be6c6bb4ffe53b0e694adb18 Mon Sep 17 00:00:00 2001 From: Pete Date: Fri, 25 Oct 2024 09:21:15 +0200 Subject: [PATCH 3/5] bug: Fix bug from rebase --- static/sass/main.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/sass/main.scss b/static/sass/main.scss index 86d6ee3..ba982a0 100644 --- a/static/sass/main.scss +++ b/static/sass/main.scss @@ -40,6 +40,8 @@ .p-filter-panel-section__chips-toggle { padding-top: 6px; margin-bottom: 18px; +} + .p-asset-card { padding:0.75rem; background:#f7f7f7; From 07047998c38ac19c8c3479747f3690927bb8ef01 Mon Sep 17 00:00:00 2001 From: Pete Date: Fri, 25 Oct 2024 09:45:23 +0200 Subject: [PATCH 4/5] bug: Fix 'name' value not being passed properly --- templates/create-update.html | 6 ++--- templates/details.html | 22 +++++++++--------- templates/update.html | 45 ------------------------------------ webapp/routes.py | 15 ++++++++---- webapp/services.py | 8 +++++-- 5 files changed, 31 insertions(+), 65 deletions(-) delete mode 100644 templates/update.html diff --git a/templates/create-update.html b/templates/create-update.html index fb9c164..4091f9d 100644 --- a/templates/create-update.html +++ b/templates/create-update.html @@ -21,7 +21,7 @@

Upload asset

{% if is_update %} -

{{ asset.file_path}}

+

{{ asset.file_path}}

@@ -62,7 +62,7 @@

Upload asset

Use any relevant naming conventions for your asset.

- +
@@ -204,7 +204,7 @@

Authors

Link to the asset on Google Drive, if applicable.

- +
diff --git a/templates/details.html b/templates/details.html index 35742ac..9f39c1b 100644 --- a/templates/details.html +++ b/templates/details.html @@ -14,10 +14,10 @@

Asset details

/> {% endif %} -

File path:{{ file_path }}

+

File path:{{ asset.file_path }}

Name: {{ asset.name }}

File type: .{{ asset.file_path.split('.', 1)[1] }}

-

Asset type: {{ asset.asset_type }}

+

Asset type: {{ asset.asset_type }}

Image size: {% if asset.data.width and asset.data.height %}{{asset.data.width}} x {{asset.data.height}}{% else %}Unknown{% endif %}

Products: @@ -31,13 +31,13 @@

Asset details

{{ tag.name }}{% if not loop.last %},{% endif %} {% endfor %}

+

Language: {{ asset.language }}

+

Salesforce campaign ID: {{ asset.salesforce_campaign_id }}

+

Google drive link: {{ asset.google_drive_link }}

+

Author email: {{ asset.author_email }}

+

Deprecated: {{ asset.deprecated }}

Created: {{ asset.created.strftime('%d %B %Y') }}

Updated: {{ asset.updated.strftime('%d %B %Y') }}

-

Google drive link: {{ asset.google_drive_link }}

-

Author email: {{ asset.author_email }}

-

Language: {{ asset.language }}

-

Salesforce campaign ID: {{ asset.salesforce_campaign_id }}

-

Deprecated: {{ asset.deprecated }}

- +

diff --git a/templates/update.html b/templates/update.html deleted file mode 100644 index fe7c1ce..0000000 --- a/templates/update.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "_layout.html" %} - -{% block title %}Update tags{% endblock %} - -{% block content %} -
-
-
-

{{ asset.file_path or request.args.get("file-path")}}

- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
-

- {{message}} -

-
- {% endfor %} - {% endif %} - {% endwith %} - {% if asset %} -
- - {% if asset.data.image %} -
- - - -
- {% endif %} - - - - -
- -
-
- {% endif %} -
-
-
-{% endblock %} \ No newline at end of file diff --git a/webapp/routes.py b/webapp/routes.py index 7b7fe21..41c80ec 100644 --- a/webapp/routes.py +++ b/webapp/routes.py @@ -114,6 +114,7 @@ def create(): "first_name": author_first_name, "last_name": author_last_name, } + name = flask.request.form.get("name", "") for asset_file in flask.request.files.getlist("assets"): try: @@ -123,6 +124,7 @@ def create(): asset = asset_service.create_asset( file_content=content, friendly_name=filename, + name=name, optimize=optimize, tags=tags, products=products, @@ -176,19 +178,21 @@ def update(): google_drive_link = request.form.get("google_drive_link", "") salesforce_campaign_id = request.form.get("salesforce_campaign_id", "") language = request.form.get("language", "") - author_email = flask.request.form.get("author_email", "") - author_first_name = flask.request.form.get("author_first_name", "") - author_last_name = flask.request.form.get("author_last_name", "") + author_email = request.form.get("author_email", "") + author_first_name = request.form.get("author_first_name", "") + author_last_name = request.form.get("author_last_name", "") author = { "email": author_email, "first_name": author_first_name, "last_name": author_last_name, } + name = request.form.get("name", "") try: asset = asset_service.update_asset( file_path=file_path, tags=tags, + name=name, deprecated=deprecated, products=products, asset_type=asset_type, @@ -200,11 +204,13 @@ def update(): flask.flash("Asset updated", "positive") except AssetNotFound: flask.flash("Asset not found", "negative") + return flask.redirect("/manager/details?file-path=" + file_path) return flask.render_template( "create-update.html", products_list=products_list, asset=asset ) + @ui_blueprint.route("/details", methods=["GET"]) @login_required def details(): @@ -213,9 +219,10 @@ def details(): asset = asset_service.find_asset(file_path) if not asset: flask.flash("Asset not found", "negative") - + return flask.render_template("details.html", asset=asset) + # API Routes # === diff --git a/webapp/services.py b/webapp/services.py index 4a6fdda..88eede0 100644 --- a/webapp/services.py +++ b/webapp/services.py @@ -82,6 +82,7 @@ def create_asset( file_content, friendly_name: str, optimize: bool, + name: str = None, url_path=None, tags: List[str] = [], products: List[str] = [], @@ -103,7 +104,7 @@ def create_asset( # First we ensure it is b64 encoded encoded_file_content = b64encode(file_content) # Then we can decode it - decoded_file_content = (b64decode(encoded_file_content)) + decoded_file_content = b64decode(encoded_file_content) if imghdr.what(None, h=file_content) is not None or is_svg( file_content @@ -165,7 +166,7 @@ def create_asset( # Save file info in Postgres asset = Asset( file_path=url_path, - name=friendly_name, + name=name, data=data, tags=tags, created=datetime.now(tz=timezone.utc), @@ -258,6 +259,7 @@ def normalize_tag_name(self, tag_name): def update_asset( self, file_path: str, + name: str = None, tags: List[str] = [], deprecated: bool = None, products: List[str] = [], @@ -278,6 +280,8 @@ def update_asset( if tags: tags = self.create_tags_if_not_exist(tags) asset.tags = tags + if name: + asset.name = name if products: products = self.create_products_if_not_exists(products) asset.products = products From e7f1b7e7e704c0869dab60df8b6567981714c06d Mon Sep 17 00:00:00 2001 From: Pete Date: Fri, 25 Oct 2024 10:14:42 +0200 Subject: [PATCH 5/5] style: Format-python --- webapp/services.py | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp/services.py b/webapp/services.py index 88eede0..10eac5e 100644 --- a/webapp/services.py +++ b/webapp/services.py @@ -5,7 +5,6 @@ from io import BytesIO # Packages -from wand.image import Image from typing import List from PIL import Image as PillowImage