From 2dd0398abce6198fe3446152992998b68aaca4b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Crespo?= <151526546+cjimenezcuah@users.noreply.github.com> Date: Fri, 11 Apr 2025 09:52:11 +0200 Subject: [PATCH 1/9] Add or update the Azure App Service build and deployment workflow config --- ...-no-infra_msdcos-poython-postgres-pap3.yml | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 .github/workflows/starter-no-infra_msdcos-poython-postgres-pap3.yml diff --git a/.github/workflows/starter-no-infra_msdcos-poython-postgres-pap3.yml b/.github/workflows/starter-no-infra_msdcos-poython-postgres-pap3.yml new file mode 100644 index 00000000..b3944d44 --- /dev/null +++ b/.github/workflows/starter-no-infra_msdcos-poython-postgres-pap3.yml @@ -0,0 +1,81 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions +# More info on Python, GitHub Actions, and Azure App Service: https://aka.ms/python-webapps-actions + +name: Build and deploy Python app to Azure Web App - msdcos-poython-postgres-pap3 + +on: + push: + branches: + - starter-no-infra + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read #This is required for actions/checkout + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python version + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Create and start virtual environment + run: | + python -m venv venv + source venv/bin/activate + + - name: Install dependencies + run: pip install -r requirements.txt + + # Optional: Add step to run tests here (PyTest, Django test suites, etc.) + + - name: Zip artifact for deployment + run: zip release.zip ./* -r + + - name: Upload artifact for deployment jobs + uses: actions/upload-artifact@v4 + with: + name: python-app + path: | + release.zip + !venv/ + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + contents: read #This is required for actions/checkout + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: python-app + + - name: Unzip artifact for deployment + run: unzip release.zip + + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_EFD29DF8146942F380ED09DAF3E21208 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_172879041B97433F9F9FA2BD6304C079 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_82F56F2DC92B439491E9354BA0348AC7 }} + + - name: 'Deploy to Azure Web App' + uses: azure/webapps-deploy@v3 + id: deploy-to-webapp + with: + app-name: 'msdcos-poython-postgres-pap3' + slot-name: 'Production' + \ No newline at end of file From f5de43f8a78d9cbaa4bc04e6549a440119a4e66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Crespo?= <151526546+cjimenezcuah@users.noreply.github.com> Date: Wed, 16 Apr 2025 17:47:49 +0200 Subject: [PATCH 2/9] Add or update the Azure App Service build and deployment workflow config --- .../starter-no-infra_msdcos-poython-postgres-pap3.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/starter-no-infra_msdcos-poython-postgres-pap3.yml b/.github/workflows/starter-no-infra_msdcos-poython-postgres-pap3.yml index b3944d44..9d9f4fd2 100644 --- a/.github/workflows/starter-no-infra_msdcos-poython-postgres-pap3.yml +++ b/.github/workflows/starter-no-infra_msdcos-poython-postgres-pap3.yml @@ -68,9 +68,9 @@ jobs: - name: Login to Azure uses: azure/login@v2 with: - client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_EFD29DF8146942F380ED09DAF3E21208 }} - tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_172879041B97433F9F9FA2BD6304C079 }} - subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_82F56F2DC92B439491E9354BA0348AC7 }} + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_15D32A133FE345938D0B68C1387A074C }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_961C12A76F67417695358C3D734F4D13 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_55D54E4C2A824EB39284FE19BA8B29CF }} - name: 'Deploy to Azure Web App' uses: azure/webapps-deploy@v3 From 44a056b389a1c6f94309ab8b071be1b8dea57e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Crespo?= <151526546+cjimenezcuah@users.noreply.github.com> Date: Wed, 16 Apr 2025 16:05:16 +0000 Subject: [PATCH 3/9] Configure Azure database connecton --- azureproject/production.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azureproject/production.py b/azureproject/production.py index 331bd036..717955e4 100644 --- a/azureproject/production.py +++ b/azureproject/production.py @@ -1,8 +1,8 @@ import os -# DATABASE_URI = 'postgresql+psycopg2://{dbuser}:{dbpass}@{dbhost}/{dbname}'.format( -# dbuser=os.getenv('AZURE_POSTGRESQL_USER'), -# dbpass=os.getenv('AZURE_POSTGRESQL_PASSWORD'), -# dbhost=os.getenv('AZURE_POSTGRESQL_HOST'), -# dbname=os.getenv('AZURE_POSTGRESQL_NAME') -# ) +DATABASE_URI = 'postgresql+psycopg2://{dbuser}:{dbpass}@{dbhost}/{dbname}'.format( + dbuser=os.getenv('AZURE_POSTGRESQL_USER'), + dbpass=os.getenv('AZURE_POSTGRESQL_PASSWORD'), + dbhost=os.getenv('AZURE_POSTGRESQL_HOST'), + dbname=os.getenv('AZURE_POSTGRESQL_NAME') + ) From d1ad5bc1ff09d47ec669628cf480e25e4a5d4ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Crespo?= <151526546+cjimenezcuah@users.noreply.github.com> Date: Thu, 17 Apr 2025 13:45:16 +0000 Subject: [PATCH 4/9] Configure Azure database connection --- app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.py b/app.py index 8fc6a89b..363aca8d 100644 --- a/app.py +++ b/app.py @@ -6,7 +6,7 @@ from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect - +# This is the main entry point for the application. app = Flask(__name__, static_folder='static') csrf = CSRFProtect(app) From e42f2974650144116be80df465d8653d7bad03b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Crespo?= <151526546+cjimenezcuah@users.noreply.github.com> Date: Wed, 7 May 2025 16:37:11 +0000 Subject: [PATCH 5/9] eee hola --- app.py | 48 ++++--- .../d0c7b8e4b57c_initial_migration.py | 45 ------- models.py | 17 +++ templates/index.html | 121 ++++++++---------- templates/uploads.html | 20 +++ 5 files changed, 117 insertions(+), 134 deletions(-) delete mode 100644 migrations/versions/d0c7b8e4b57c_initial_migration.py create mode 100644 templates/uploads.html diff --git a/app.py b/app.py index 363aca8d..11a6976f 100644 --- a/app.py +++ b/app.py @@ -1,22 +1,19 @@ import os from datetime import datetime - -from flask import Flask, redirect, render_template, request, send_from_directory, url_for +from flask import Flask, redirect, render_template, request, send_from_directory, url_for, jsonify from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect -# This is the main entry point for the application. +# Main application app = Flask(__name__, static_folder='static') csrf = CSRFProtect(app) -# WEBSITE_HOSTNAME exists only in production environment +# Configuración según entorno if 'WEBSITE_HOSTNAME' not in os.environ: - # local development, where we'll use environment variables print("Loading config.development and environment variables from .env file.") app.config.from_object('azureproject.development') else: - # production print("Loading config.production.") app.config.from_object('azureproject.production') @@ -25,14 +22,12 @@ SQLALCHEMY_TRACK_MODIFICATIONS=False, ) -# Initialize the database connection +# Inicializar base de datos db = SQLAlchemy(app) - -# Enable Flask-Migrate commands "flask db init/migrate/upgrade" to work migrate = Migrate(app, db) -# The import must be done after db initialization due to circular import issue -from models import Restaurant, Review +# Importar modelos después de inicializar db +from models import Restaurant, Review, ImageData @app.route('/', methods=['GET']) def index(): @@ -59,7 +54,6 @@ def add_restaurant(): street_address = request.values.get('street_address') description = request.values.get('description') except (KeyError): - # Redisplay the question voting form. return render_template('add_restaurant.html', { 'error_message': "You must include a restaurant name, address, and description", }) @@ -70,7 +64,6 @@ def add_restaurant(): restaurant.description = description db.session.add(restaurant) db.session.commit() - return redirect(url_for('details', id=restaurant.id)) @app.route('/review/', methods=['POST']) @@ -81,7 +74,6 @@ def add_review(id): rating = request.values.get('rating') review_text = request.values.get('review_text') except (KeyError): - #Redisplay the question voting form. return render_template('add_review.html', { 'error_message': "Error adding review", }) @@ -101,19 +93,39 @@ def add_review(id): def utility_processor(): def star_rating(id): reviews = Review.query.where(Review.restaurant == id) - ratings = [] review_count = 0 for review in reviews: - ratings += [review.rating] + ratings.append(review.rating) review_count += 1 - avg_rating = sum(ratings) / len(ratings) if ratings else 0 stars_percent = round((avg_rating / 5.0) * 100) if review_count > 0 else 0 return {'avg_rating': avg_rating, 'review_count': review_count, 'stars_percent': stars_percent} - return dict(star_rating=star_rating) +@app.route('/images', methods=['GET']) +def image_table(): + print('Request for image table page received') + images = ImageData.query.order_by(ImageData.upload_time.desc()).all() + return render_template('image_table.html', images=images) + +@app.route('/api/images', methods=['GET']) +def image_json(): + images = ImageData.query.order_by(ImageData.upload_time.desc()).all() + data = [ + { + "id": img.id, + "filename": img.filename, + "username": img.username, + "upload_time": img.upload_time.isoformat(), + "pixel_rojo": img.pixel_rojo, + "pixel_verde": img.pixel_verde, + "pixel_azul": img.pixel_azul + } + for img in images + ] + return jsonify(data) + @app.route('/favicon.ico') def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), diff --git a/migrations/versions/d0c7b8e4b57c_initial_migration.py b/migrations/versions/d0c7b8e4b57c_initial_migration.py deleted file mode 100644 index 1a5d874d..00000000 --- a/migrations/versions/d0c7b8e4b57c_initial_migration.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Initial migration. - -Revision ID: d0c7b8e4b57c -Revises: -Create Date: 2022-11-08 17:00:02.151921 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'd0c7b8e4b57c' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('restaurant', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=50), nullable=True), - sa.Column('street_address', sa.String(length=50), nullable=True), - sa.Column('description', sa.String(length=250), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('review', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('restaurant', sa.Integer(), nullable=True), - sa.Column('user_name', sa.String(length=30), nullable=True), - sa.Column('rating', sa.Integer(), nullable=True), - sa.Column('review_text', sa.String(length=500), nullable=True), - sa.Column('review_date', sa.DateTime(), nullable=True), - sa.ForeignKeyConstraint(['restaurant'], ['restaurant.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('review') - op.drop_table('restaurant') - # ### end Alembic commands ### \ No newline at end of file diff --git a/models.py b/models.py index 7f3dac97..fb056727 100644 --- a/models.py +++ b/models.py @@ -1,5 +1,6 @@ from sqlalchemy import Column, DateTime, ForeignKey, Integer, String from sqlalchemy.orm import validates +from datetime import datetime from app import db @@ -30,3 +31,19 @@ def validate_rating(self, key, value): def __str__(self): return f"{self.user_name}: {self.review_date:%x}" + + + +class ImageData(db.Model): + __tablename__ = 'image_data' + + id = Column(Integer, primary_key=True) + filename = Column(String(255), nullable=False) + pixel_red = Column(String, nullable=False) # Ej: "1234" + pixel_green = Column(String, nullable=False) # Ej: "5678" + pixel_blue = Column(String, nullable=False) # Ej: "9101" + username = Column(String(100), nullable=False) + upload_time = Column(DateTime, nullable=False, default=datetime.utcnow) + + def __repr__(self): + return f"" \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 638e8227..ec16b8a1 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,72 +1,51 @@ -{% extends "base.html" %} -{% block title %}Restaurant List{% endblock %} -{% block head %} - {{ super() }} + + + + + Tabla de Imágenes Subidas -{% endblock %} -{% block content %} -

Restaurants

- - {% if True %} - - - - - - - - - - {% for restaurant in restaurants %} - - - - - - {% endfor %} - -
NameRatingDetails
{{ restaurant.name }}{% include "star_rating.html" %}Details
- {% else %} -

No restaurants exist. Select Add new restaurant to add one.

- {% endif %} - - -{% endblock %} \ No newline at end of file + table { + border-collapse: collapse; + width: 90%; + margin: auto; + } + th, td { + border: 1px solid #ccc; + padding: 8px; + text-align: center; + } + th { + background-color: #f3f3f3; + } + + + +

Imágenes Subidas

+ + + + + + + + + + + + + + {% for img in images %} + + + + + + + + + + {% endfor %} + +
IDArchivoUsuarioFechaNº Pixeles RojosNº Pixeles VerdesNº Pixeles Azules
{{ img.id }}{{ img.filename }}{{ img.username }}{{ img.upload_time }}{{ img.pixel_rojo }}{{ img.pixel_verde }}{{ img.pixel_azul }}
+ + diff --git a/templates/uploads.html b/templates/uploads.html new file mode 100644 index 00000000..6c2c0c79 --- /dev/null +++ b/templates/uploads.html @@ -0,0 +1,20 @@ + + + + + Uploads + + +

Uploaded Images

+ {% for upload in uploads %} +
+

{{ upload.file_name }}

+

Uploaded by: {{ upload.user_name }}

+

Red Pixels: {{ upload.red_pixels }}

+

Green Pixels: {{ upload.green_pixels }}

+

Blue Pixels: {{ upload.blue_pixels }}

+ Uploaded Image +
+ {% endfor %} + + From d392ffe476de0e5baf75668822347379e12b0f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Crespo?= <151526546+cjimenezcuah@users.noreply.github.com> Date: Wed, 7 May 2025 16:44:21 +0000 Subject: [PATCH 6/9] eeee hola --- app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.py b/app.py index 11a6976f..85ebfdbc 100644 --- a/app.py +++ b/app.py @@ -13,7 +13,7 @@ if 'WEBSITE_HOSTNAME' not in os.environ: print("Loading config.development and environment variables from .env file.") app.config.from_object('azureproject.development') -else: +else: print("Loading config.production.") app.config.from_object('azureproject.production') From 448a3bd0957bf6e1866a5c020d18644d4a61085f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Crespo?= <151526546+cjimenezcuah@users.noreply.github.com> Date: Thu, 8 May 2025 22:34:11 +0000 Subject: [PATCH 7/9] aaaaa --- app.py | 89 ++++++++---- azureproject/production.py | 2 +- ...9c1dc_recrear_migraci\303\263n_perdida.py" | 24 ++++ .../versions/1c80845faa01_unify_heads.py | 24 ++++ migrations/versions/2b81d0790f10_a.py | 37 +++++ .../7d50f8ce1dd1_regenerar_migraciones.py | 50 +++++++ migrations/versions/e95f32cb7145_b.py | 37 +++++ .../f7006424e5cd_initial_migration.py | 67 +++++++++ models.py | 13 +- templates/index.html | 130 +++++++++++------- 10 files changed, 388 insertions(+), 85 deletions(-) create mode 100644 "migrations/versions/1920d7e9c1dc_recrear_migraci\303\263n_perdida.py" create mode 100644 migrations/versions/1c80845faa01_unify_heads.py create mode 100644 migrations/versions/2b81d0790f10_a.py create mode 100644 migrations/versions/7d50f8ce1dd1_regenerar_migraciones.py create mode 100644 migrations/versions/e95f32cb7145_b.py create mode 100644 migrations/versions/f7006424e5cd_initial_migration.py diff --git a/app.py b/app.py index 85ebfdbc..237ec770 100644 --- a/app.py +++ b/app.py @@ -1,19 +1,22 @@ import os from datetime import datetime -from flask import Flask, redirect, render_template, request, send_from_directory, url_for, jsonify + +from flask import Flask, redirect, render_template, request, send_from_directory, url_for from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect -# Main application + app = Flask(__name__, static_folder='static') csrf = CSRFProtect(app) -# Configuración según entorno +# WEBSITE_HOSTNAME exists only in production environment if 'WEBSITE_HOSTNAME' not in os.environ: + # local development, where we'll use environment variables print("Loading config.development and environment variables from .env file.") app.config.from_object('azureproject.development') -else: +else: + # production print("Loading config.production.") app.config.from_object('azureproject.production') @@ -22,18 +25,26 @@ SQLALCHEMY_TRACK_MODIFICATIONS=False, ) -# Inicializar base de datos +# Initialize the database connection db = SQLAlchemy(app) + +# Enable Flask-Migrate commands "flask db init/migrate/upgrade" to work migrate = Migrate(app, db) -# Importar modelos después de inicializar db +# The import must be done after db initialization due to circular import issue from models import Restaurant, Review, ImageData +#@app.route('/', methods=['GET']) +#def index(): +# print('Request for index page received') +# restaurants = Restaurant.query.all() +# return render_template('index.html', restaurants=restaurants) + @app.route('/', methods=['GET']) def index(): print('Request for index page received') - restaurants = Restaurant.query.all() - return render_template('index.html', restaurants=restaurants) + images = ImageData.query.all() + return render_template('index.html', images=images) @app.route('/', methods=['GET']) def details(id): @@ -54,6 +65,7 @@ def add_restaurant(): street_address = request.values.get('street_address') description = request.values.get('description') except (KeyError): + # Redisplay the question voting form. return render_template('add_restaurant.html', { 'error_message': "You must include a restaurant name, address, and description", }) @@ -64,6 +76,7 @@ def add_restaurant(): restaurant.description = description db.session.add(restaurant) db.session.commit() + return redirect(url_for('details', id=restaurant.id)) @app.route('/review/', methods=['POST']) @@ -74,6 +87,7 @@ def add_review(id): rating = request.values.get('rating') review_text = request.values.get('review_text') except (KeyError): + #Redisplay the question voting form. return render_template('add_review.html', { 'error_message': "Error adding review", }) @@ -93,38 +107,61 @@ def add_review(id): def utility_processor(): def star_rating(id): reviews = Review.query.where(Review.restaurant == id) + ratings = [] review_count = 0 for review in reviews: - ratings.append(review.rating) + ratings += [review.rating] review_count += 1 + avg_rating = sum(ratings) / len(ratings) if ratings else 0 stars_percent = round((avg_rating / 5.0) * 100) if review_count > 0 else 0 return {'avg_rating': avg_rating, 'review_count': review_count, 'stars_percent': stars_percent} + return dict(star_rating=star_rating) @app.route('/images', methods=['GET']) def image_table(): print('Request for image table page received') images = ImageData.query.order_by(ImageData.upload_time.desc()).all() - return render_template('image_table.html', images=images) + return render_template('index.html', images=images) -@app.route('/api/images', methods=['GET']) -def image_json(): - images = ImageData.query.order_by(ImageData.upload_time.desc()).all() - data = [ - { - "id": img.id, - "filename": img.filename, - "username": img.username, - "upload_time": img.upload_time.isoformat(), - "pixel_rojo": img.pixel_rojo, - "pixel_verde": img.pixel_verde, - "pixel_azul": img.pixel_azul - } - for img in images - ] - return jsonify(data) +@csrf.exempt +@app.route('/upload_image', methods=['POST']) +def upload_image(): + print('Request to upload image received') + if not request.is_json: + return jsonify({"error": "Request must be JSON"}), 400 + + data = request.get_json() + filename = data.get('filename') + pixel_red = data.get('pixel_red') + pixel_green = data.get('pixel_green') + pixel_blue = data.get('pixel_blue') + username = data.get('username') + + + if not all([filename, pixel_red, pixel_green, pixel_blue, username]): + return jsonify({"error": "All fields ('filename', 'pixel_red', 'pixel_green', 'pixel_blue', 'username') are required"}), 400 + + try: + new_image = ImageData( + filename=filename, + pixel_red=pixel_red, + pixel_green=pixel_green, + pixel_blue=pixel_blue, + username=username, + upload_time=datetime.now(timezone.utc) + + ) + #db.session.create_all() + #db.session.commit() + db.session.add(new_image) + db.session.commit() + return jsonify({"message": "Image uploaded successfully"}), 201 + except Exception as e: + db.session.rollback() + return jsonify({"error": str(e)}), 500 @app.route('/favicon.ico') def favicon(): diff --git a/azureproject/production.py b/azureproject/production.py index 717955e4..314ec193 100644 --- a/azureproject/production.py +++ b/azureproject/production.py @@ -5,4 +5,4 @@ dbpass=os.getenv('AZURE_POSTGRESQL_PASSWORD'), dbhost=os.getenv('AZURE_POSTGRESQL_HOST'), dbname=os.getenv('AZURE_POSTGRESQL_NAME') - ) +) diff --git "a/migrations/versions/1920d7e9c1dc_recrear_migraci\303\263n_perdida.py" "b/migrations/versions/1920d7e9c1dc_recrear_migraci\303\263n_perdida.py" new file mode 100644 index 00000000..a9e45913 --- /dev/null +++ "b/migrations/versions/1920d7e9c1dc_recrear_migraci\303\263n_perdida.py" @@ -0,0 +1,24 @@ +"""Recrear migración perdida + +Revision ID: 1920d7e9c1dc +Revises: +Create Date: 2025-05-08 17:10:57.001491 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1920d7e9c1dc' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/migrations/versions/1c80845faa01_unify_heads.py b/migrations/versions/1c80845faa01_unify_heads.py new file mode 100644 index 00000000..6fa448e5 --- /dev/null +++ b/migrations/versions/1c80845faa01_unify_heads.py @@ -0,0 +1,24 @@ +"""Unify heads + +Revision ID: 1c80845faa01 +Revises: 7d50f8ce1dd1, e95f32cb7145 +Create Date: 2025-05-08 22:28:42.064772 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1c80845faa01' +down_revision = ('7d50f8ce1dd1', 'e95f32cb7145') +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/migrations/versions/2b81d0790f10_a.py b/migrations/versions/2b81d0790f10_a.py new file mode 100644 index 00000000..dda1960f --- /dev/null +++ b/migrations/versions/2b81d0790f10_a.py @@ -0,0 +1,37 @@ +"""a + +Revision ID: 2b81d0790f10 +Revises: f7006424e5cd +Create Date: 2025-05-08 21:56:08.485113 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '2b81d0790f10' +down_revision = 'f7006424e5cd' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('image_data') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('image_data', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('filename', sa.VARCHAR(length=255), autoincrement=False, nullable=False), + sa.Column('pixel_red', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('pixel_green', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('pixel_blue', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('username', sa.VARCHAR(length=100), autoincrement=False, nullable=False), + sa.Column('upload_time', postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('id', name='image_data_pkey') + ) + # ### end Alembic commands ### diff --git a/migrations/versions/7d50f8ce1dd1_regenerar_migraciones.py b/migrations/versions/7d50f8ce1dd1_regenerar_migraciones.py new file mode 100644 index 00000000..27bc04f4 --- /dev/null +++ b/migrations/versions/7d50f8ce1dd1_regenerar_migraciones.py @@ -0,0 +1,50 @@ +"""Regenerar migraciones + +Revision ID: 7d50f8ce1dd1 +Revises: 1920d7e9c1dc +Create Date: 2025-05-08 17:11:06.789172 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '7d50f8ce1dd1' +down_revision = '1920d7e9c1dc' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('image_data', schema=None) as batch_op: + batch_op.add_column(sa.Column('pixel_red', sa.String(), nullable=False)) + batch_op.add_column(sa.Column('pixel_green', sa.String(), nullable=False)) + batch_op.add_column(sa.Column('pixel_blue', sa.String(), nullable=False)) + batch_op.add_column(sa.Column('upload_time', sa.DateTime(), nullable=False)) + batch_op.alter_column('username', + existing_type=sa.VARCHAR(length=50), + type_=sa.String(length=100), + existing_nullable=False) + batch_op.drop_column('pixel_data') + batch_op.drop_column('upload_datetime') + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('image_data', schema=None) as batch_op: + batch_op.add_column(sa.Column('upload_datetime', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) + batch_op.add_column(sa.Column('pixel_data', sa.TEXT(), autoincrement=False, nullable=False)) + batch_op.alter_column('username', + existing_type=sa.String(length=100), + type_=sa.VARCHAR(length=50), + existing_nullable=False) + batch_op.drop_column('upload_time') + batch_op.drop_column('pixel_blue') + batch_op.drop_column('pixel_green') + batch_op.drop_column('pixel_red') + + # ### end Alembic commands ### diff --git a/migrations/versions/e95f32cb7145_b.py b/migrations/versions/e95f32cb7145_b.py new file mode 100644 index 00000000..16747869 --- /dev/null +++ b/migrations/versions/e95f32cb7145_b.py @@ -0,0 +1,37 @@ +"""b + +Revision ID: e95f32cb7145 +Revises: 2b81d0790f10 +Create Date: 2025-05-08 22:07:14.980769 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e95f32cb7145' +down_revision = '2b81d0790f10' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('image_data', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('filename', sa.String(length=255), nullable=False), + sa.Column('pixel_red', sa.Integer(), nullable=False), + sa.Column('pixel_green', sa.Integer(), nullable=False), + sa.Column('pixel_blue', sa.Integer(), nullable=False), + sa.Column('username', sa.String(length=100), nullable=False), + sa.Column('upload_time', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('image_data') + # ### end Alembic commands ### diff --git a/migrations/versions/f7006424e5cd_initial_migration.py b/migrations/versions/f7006424e5cd_initial_migration.py new file mode 100644 index 00000000..a36045f9 --- /dev/null +++ b/migrations/versions/f7006424e5cd_initial_migration.py @@ -0,0 +1,67 @@ +"""Initial migration. + +Revision ID: f7006424e5cd +Revises: +Create Date: 2022-11-08 17:00:02.151921 + +""" +from sqlalchemy import inspect +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f7006424e5cd' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + bind = op.get_bind() + inspector = inspect(bind) + if 'restaurant' not in inspector.get_table_names(): + op.create_table( + 'restaurant', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('name', sa.String(length=50)), + sa.Column('street_address', sa.String(length=50)), + sa.Column('description', sa.String(length=250)) + ) + if 'review' not in inspector.get_table_names(): + op.create_table( + 'review', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('restaurant', sa.Integer(), sa.ForeignKey('restaurant.id', ondelete="CASCADE")), + sa.Column('user_name', sa.String(length=30)), + sa.Column('rating', sa.Integer()), + sa.Column('review_text', sa.String(length=500)), + sa.Column('review_date', sa.DateTime()) + ) + + # ### commands auto generated by Alembic - please adjust! ### + #op.create_table('restaurant', + #sa.Column('id', sa.Integer(), nullable=False), + #sa.Column('name', sa.String(length=50), nullable=True), + #sa.Column('street_address', sa.String(length=50), nullable=True), + #sa.Column('description', sa.String(length=250), nullable=True), + #sa.PrimaryKeyConstraint('id') + #) + #op.create_table('review', + #sa.Column('id', sa.Integer(), nullable=False), + #sa.Column('restaurant', sa.Integer(), nullable=True), + #sa.Column('user_name', sa.String(length=30), nullable=True), + #sa.Column('rating', sa.Integer(), nullable=True), + #sa.Column('review_text', sa.String(length=500), nullable=True), + #sa.Column('review_date', sa.DateTime(), nullable=True), + #sa.ForeignKeyConstraint(['restaurant'], ['restaurant.id'], ondelete='CASCADE'), + #sa.PrimaryKeyConstraint('id') + #) + ## ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('review') + op.drop_table('restaurant') + # ### end Alembic commands ### \ No newline at end of file diff --git a/models.py b/models.py index fb056727..e88ccf78 100644 --- a/models.py +++ b/models.py @@ -1,6 +1,5 @@ from sqlalchemy import Column, DateTime, ForeignKey, Integer, String from sqlalchemy.orm import validates -from datetime import datetime from app import db @@ -32,18 +31,16 @@ def validate_rating(self, key, value): def __str__(self): return f"{self.user_name}: {self.review_date:%x}" - - class ImageData(db.Model): __tablename__ = 'image_data' id = Column(Integer, primary_key=True) filename = Column(String(255), nullable=False) - pixel_red = Column(String, nullable=False) # Ej: "1234" - pixel_green = Column(String, nullable=False) # Ej: "5678" - pixel_blue = Column(String, nullable=False) # Ej: "9101" + pixel_red = Column(Integer, nullable=False) + pixel_green = Column(Integer, nullable=False) # + pixel_blue = Column(Integer, nullable=False) username = Column(String(100), nullable=False) - upload_time = Column(DateTime, nullable=False, default=datetime.utcnow) + upload_time = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) def __repr__(self): - return f"" \ No newline at end of file + return f"" diff --git a/templates/index.html b/templates/index.html index ec16b8a1..a568262a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,51 +1,81 @@ - - - - - Tabla de Imágenes Subidas +{% extends "base.html" %} +{% block title %}Restaurant List{% endblock %} +{% block head %} + {{ super() }} - - -

Imágenes Subidas

- - - - - - - - - - - - - - {% for img in images %} - - - - - - - - - - {% endfor %} - -
IDArchivoUsuarioFechaNº Pixeles RojosNº Pixeles VerdesNº Pixeles Azules
{{ img.id }}{{ img.filename }}{{ img.username }}{{ img.upload_time }}{{ img.pixel_rojo }}{{ img.pixel_verde }}{{ img.pixel_azul }}
- - + body { + min-height: 75rem; + padding-top: 4.5rem; + } + + .score { + display: block; + font-size: 16px; + position: relative; + overflow: hidden; + } + + .score-wrap { + display: inline-block; + position: relative; + height: 19px; + } + + .score .stars-active { + color: #EEBD01; + position: relative; + z-index: 10; + display: inline-block; + overflow: hidden; + white-space: nowrap; + } + + .score .stars-inactive { + color: grey; + position: absolute; + top: 0; + left: 0; + -webkit-text-stroke: initial; + /* overflow: hidden; */ + } + +{% endblock %} +{% block content %} +

Restaurants

+ + {% if True %} + + + + + + + + + + + + + {% for image in images %} + + + + + + + + + + + + {% endfor %} + +
UsuarioNombre imagenRojoverdeazulFecha
{{ image.username }}{{ image.filename }}{{ image.pixel_red }}{{ image.pixel_green }}{{ image.pixel_blue }}{{ image.upload_time }}
+ {% else %} +

No restaurants exist. Select Add new restaurant to add one.

+ {% endif %} + + +{% endblock %} \ No newline at end of file From 14b051c73f8145690843a9769a274bffb3434f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Crespo?= <151526546+cjimenezcuah@users.noreply.github.com> Date: Thu, 8 May 2025 23:19:35 +0000 Subject: [PATCH 8/9] aeaea --- app.py | 4 ++-- migrations/versions/1c80845faa01_unify_heads.py | 4 ++-- models.py | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app.py b/app.py index 237ec770..e9c6d589 100644 --- a/app.py +++ b/app.py @@ -1,7 +1,7 @@ import os -from datetime import datetime +from datetime import datetime, timezone -from flask import Flask, redirect, render_template, request, send_from_directory, url_for +from flask import Flask, jsonify, redirect, render_template, request, send_from_directory, url_for from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect diff --git a/migrations/versions/1c80845faa01_unify_heads.py b/migrations/versions/1c80845faa01_unify_heads.py index 6fa448e5..a970c9ca 100644 --- a/migrations/versions/1c80845faa01_unify_heads.py +++ b/migrations/versions/1c80845faa01_unify_heads.py @@ -1,6 +1,6 @@ """Unify heads -Revision ID: 1c80845faa01 +Revision ID: d0c7b8e4b57c Revises: 7d50f8ce1dd1, e95f32cb7145 Create Date: 2025-05-08 22:28:42.064772 @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '1c80845faa01' +revision = 'd0c7b8e4b57c' down_revision = ('7d50f8ce1dd1', 'e95f32cb7145') branch_labels = None depends_on = None diff --git a/models.py b/models.py index e88ccf78..2556963e 100644 --- a/models.py +++ b/models.py @@ -1,3 +1,4 @@ +from datetime import datetime, timezone from sqlalchemy import Column, DateTime, ForeignKey, Integer, String from sqlalchemy.orm import validates From 906e15c755b59119a3679c5d20a33eec30bc029e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Crespo?= <151526546+cjimenezcuah@users.noreply.github.com> Date: Thu, 8 May 2025 23:44:28 +0000 Subject: [PATCH 9/9] aeaeaffffff --- templates/base.html | 2 +- templates/index.html | 144 ++++++++++++++++++++++++++++++------------- 2 files changed, 101 insertions(+), 45 deletions(-) diff --git a/templates/base.html b/templates/base.html index 78fb4061..3881a3fb 100644 --- a/templates/base.html +++ b/templates/base.html @@ -13,7 +13,7 @@
- Azure Restaurant Review + Scalacomp Imágenes Review