Skip to content

Profiles: Added dynamic fields #16

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

Merged
merged 1 commit into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
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
22 changes: 13 additions & 9 deletions .env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# True for development, False for production
DEBUG=False
DEBUG=True

# Flask ENV
FLASK_APP=run.py
Expand All @@ -8,14 +8,18 @@ FLASK_DEBUG=1
# If not provided, a random one is generated
# SECRET_KEY=<YOUR_SUPER_KEY_HERE>

# Used for CDN (in production)
# No Slash at the end
ASSETS_ROOT=/static/assets

# If DB credentials (if NOT provided, or wrong values SQLite is used)
# If DEBUG=False (production mode)
# DB_ENGINE=mysql
# DB_HOST=localhost
# DB_NAME=appseed_db
# DB_USERNAME=appseed_db_usr
# DB_PASS=pass
# DB_HOST=localhost
# DB_PORT=3306
# DB_USERNAME=appseed_db_usr
# DB_PASS=<STRONG_PASS>

# SOCIAL AUTH Github
# GITHUB_ID=YOUR_GITHUB_ID
# GITHUB_SECRET=YOUR_GITHUB_SECRET

# SOCIAL AUTH Google
# GOOGLE_ID=YOUR_GOOGLE_ID
# GOOGLE_SECRET=YOUR_GOOGLE_SECRET
2 changes: 1 addition & 1 deletion apps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"""

import os

from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
Expand Down Expand Up @@ -34,6 +33,7 @@ def create_app(config):
STATIC_FOLDER = os.path.join(templates_dir,'static')

print(' > TEMPLATES_FOLDER: ' + TEMPLATES_FOLDER)
print(' > STATIC_FOLDER: ' + STATIC_FOLDER)

app = Flask(__name__, static_url_path=static_prefix, template_folder=TEMPLATES_FOLDER, static_folder=STATIC_FOLDER)

Expand Down
7 changes: 3 additions & 4 deletions apps/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,15 @@ class Users(db.Model, UserMixin):

id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True)
first_name = db.Column(db.String(100), nullable=True)
last_name = db.Column(db.String(100), nullable=True)
address = db.Column(db.String(100), nullable=True)
bio = db.Column(db.String(200), nullable=True)
email = db.Column(db.String(64), unique=True)
password = db.Column(db.LargeBinary)
bio = db.Column(db.Text(), nullable=True)

oauth_github = db.Column(db.String(100), nullable=True)
oauth_google = db.Column(db.String(100), nullable=True)

readonly_fields = ["id", "username", "email", "oauth_github", "oauth_google"]

def __init__(self, **kwargs):
for property, value in kwargs.items():
# depending on whether value is an iterable or not, we must
Expand Down
78 changes: 60 additions & 18 deletions apps/home/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
"""
Copyright (c) 2019 - present AppSeed.us
"""

import wtforms
from apps.home import blueprint
from flask import render_template, request, redirect, url_for
from jinja2 import TemplateNotFound
from flask_login import login_required, current_user
from apps import db
from apps.authentication.models import Users
from flask_wtf import FlaskForm

@blueprint.route('/')
@blueprint.route('/index')
Expand All @@ -31,28 +33,68 @@ def virtual_reality():
return render_template('pages/virtual-reality.html', segment='virtual_reality')


def getField(column):
if isinstance(column.type, db.Text):
return wtforms.TextAreaField(column.name.title())
if isinstance(column.type, db.String):
return wtforms.StringField(column.name.title())
if isinstance(column.type, db.Boolean):
return wtforms.BooleanField(column.name.title())
if isinstance(column.type, db.Integer):
return wtforms.IntegerField(column.name.title())
if isinstance(column.type, db.Float):
return wtforms.DecimalField(column.name.title())
if isinstance(column.type, db.LargeBinary):
return wtforms.HiddenField(column.name.title())
return wtforms.StringField(column.name.title())


@blueprint.route('/profile', methods=['GET', 'POST'])
@login_required
def profile():
if request.method == 'POST':
first_name = request.form.get('first_name')
last_name = request.form.get('last_name')
address = request.form.get('address')
bio = request.form.get('bio')

current_user.first_name = first_name
current_user.last_name = last_name
current_user.address = address
current_user.bio = bio

try:
db.session.commit()
except Exception as e:
db.session.rollback()

return redirect(url_for('home_blueprint.profile'))
class ProfileForm(FlaskForm):
pass

readonly_fields = Users.readonly_fields
full_width_fields = {"bio"}

for column in Users.__table__.columns:
if column.name == "id":
continue

return render_template('pages/profile.html', segment='profile')
field_name = column.name
if field_name in full_width_fields:
continue

field = getField(column)
setattr(ProfileForm, field_name, field)

for field_name in full_width_fields:
if field_name in Users.__table__.columns:
column = Users.__table__.columns[field_name]
field = getField(column)
setattr(ProfileForm, field_name, field)

form = ProfileForm(obj=current_user)

if form.validate_on_submit():
readonly_fields.append("password")
excluded_fields = readonly_fields
for field_name, field_value in form.data.items():
if field_name not in excluded_fields:
setattr(current_user, field_name, field_value)

db.session.commit()
return redirect(url_for('home_blueprint.profile'))

context = {
'segment': 'profile',
'form': form,
'readonly_fields': readonly_fields,
'full_width_fields': full_width_fields,
}
return render_template('pages/profile.html', **context)


# Helper - Extract current page name from request
Expand Down
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@
"scripts": {
"dev": "vite build --watch --mode development",
"build": "vite build --mode production && npm run minify-css",
"minify-css": "cssnano static/assets/css/*.css --dir static/assets/css --no-map --suffix .min"
"minify-css": "postcss static/assets/css/*.css --dir static/assets/css --no-map --ext .min.css"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"autoprefixer": "^10.4.20",
"cssnano": "^7.0.6",
"postcss": "^8.5.3",
"postcss-cli": "^11.0.0",
"sass": "^1.85.1",
"vite": "^6.2.0"
},
"dependencies": {
"fast-glob": "^3.3.3"
}
}
}
7 changes: 7 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
plugins: [
require('cssnano')({
preset: 'default',
}),
],
};
1 change: 0 additions & 1 deletion run.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
app.logger.info('DEBUG = ' + str(DEBUG) )
app.logger.info('Page Compression = ' + 'FALSE' if DEBUG else 'TRUE' )
app.logger.info('DBMS = ' + app_config.SQLALCHEMY_DATABASE_URI)
app.logger.info('ASSETS_ROOT = ' + app_config.ASSETS_ROOT )

if __name__ == "__main__":
app.run()
74 changes: 23 additions & 51 deletions templates/pages/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,56 +15,28 @@
<h5>Edit Info</h5>
</div>
<div class="card-block px-4">
<form class="row" method="POST" action="{{ url_for('home_blueprint.profile') }}">
<div class="col-sm-6">
<div class="form-group">
<label for="exampleInputUsername">Username</label>
<input class="form-control" id="exampleInputUsername" readonly
value="{{ current_user.username }}" aria-describedby="userHelp" placeholder="Enter username">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label {% if not current_user.email %} class="text-danger" {% endif %} for="exampleInputEmail1">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" readonly
value="{{ current_user.email }}" aria-describedby="emailHelp" placeholder="Enter email">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="fn">First Name</label>
<input type="text" name="first_name" class="form-control" id="fn"
value="{{ current_user.first_name or '' }}" placeholder="Your name">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="ln">Last Name</label>
<input type="text" name="last_name" class="form-control" id="ln"
value="{{ current_user.last_name or '' }}" placeholder="Your last name">
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="add">Address</label>
<input type="text" name="address" class="form-control" id="add"
value="{{ current_user.address or '' }}" placeholder="Full address here">
<small id="addd" class="form-text text-muted">This is your shipments address</small>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="abt">About Info</label>
<textarea name="bio" class="form-control" id="abt" placeholder="Bio">{{ current_user.bio or '' }}</textarea>
<small id="abf" class="form-text text-muted">We'll show this on your profile.</small>
</div>
</div>
<div class="col-sm-12 mb-2">
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
<form class="row" method="POST">
{{ form.hidden_tag() }}

{% for field in form %}
{% if field.type in ['CSRFTokenField', 'HiddenField'] %}
{{ field() }}
{% else %}
<div class="{% if field.name in full_width_fields %}col-sm-12{% else %}col-sm-6{% endif %}">
<div class="form-group">
<label for="" class="form-label">{{ field.name|replace_value("_") }} {% if field.name in readonly_fields %}(read-only){% endif %} </label>
{{ field(class_="form-control", readonly=True if field.name in readonly_fields else False) }}
</div>
</div>
{% endif %}
{% endfor %}

<div class="col-sm-12 mb-2">
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
</div>
</div>
Expand All @@ -73,7 +45,7 @@ <h5>Edit Info</h5>
<div class="card-block">
<div class="d-flex align-items-center justify-content-center flex-column">
<div class="w-50 p-3">
<img src="{{ config.ASSETS_ROOT }}/images/user/profile.jpg" alt="profile image"
<img src="{{ url_for('static', filename='assets/img/bruce-mars.jpg') }}" alt="profile image"
class="img-fluid rounded-circle">
</div>
<div class="text-center">
Expand Down