Skip to content

Commit 638dbef

Browse files
Profiles: Added dynamic fields
1 parent d69517b commit 638dbef

File tree

8 files changed

+111
-89
lines changed

8 files changed

+111
-89
lines changed

.env

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# True for development, False for production
2-
DEBUG=False
2+
DEBUG=True
33

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

11-
# Used for CDN (in production)
12-
# No Slash at the end
13-
ASSETS_ROOT=/static/assets
14-
15-
# If DB credentials (if NOT provided, or wrong values SQLite is used)
11+
# If DEBUG=False (production mode)
1612
# DB_ENGINE=mysql
17-
# DB_HOST=localhost
1813
# DB_NAME=appseed_db
19-
# DB_USERNAME=appseed_db_usr
20-
# DB_PASS=pass
14+
# DB_HOST=localhost
2115
# DB_PORT=3306
16+
# DB_USERNAME=appseed_db_usr
17+
# DB_PASS=<STRONG_PASS>
18+
19+
# SOCIAL AUTH Github
20+
# GITHUB_ID=YOUR_GITHUB_ID
21+
# GITHUB_SECRET=YOUR_GITHUB_SECRET
22+
23+
# SOCIAL AUTH Google
24+
# GOOGLE_ID=YOUR_GOOGLE_ID
25+
# GOOGLE_SECRET=YOUR_GOOGLE_SECRET

apps/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"""
55

66
import os
7-
87
from flask import Flask
98
from flask_login import LoginManager
109
from flask_sqlalchemy import SQLAlchemy
@@ -34,6 +33,7 @@ def create_app(config):
3433
STATIC_FOLDER = os.path.join(templates_dir,'static')
3534

3635
print(' > TEMPLATES_FOLDER: ' + TEMPLATES_FOLDER)
36+
print(' > STATIC_FOLDER: ' + STATIC_FOLDER)
3737

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

apps/authentication/models.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,15 @@ class Users(db.Model, UserMixin):
1818

1919
id = db.Column(db.Integer, primary_key=True)
2020
username = db.Column(db.String(64), unique=True)
21-
first_name = db.Column(db.String(100), nullable=True)
22-
last_name = db.Column(db.String(100), nullable=True)
23-
address = db.Column(db.String(100), nullable=True)
24-
bio = db.Column(db.String(200), nullable=True)
2521
email = db.Column(db.String(64), unique=True)
2622
password = db.Column(db.LargeBinary)
23+
bio = db.Column(db.Text(), nullable=True)
2724

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

28+
readonly_fields = ["id", "username", "email", "oauth_github", "oauth_google"]
29+
3130
def __init__(self, **kwargs):
3231
for property, value in kwargs.items():
3332
# depending on whether value is an iterable or not, we must

apps/home/routes.py

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
"""
33
Copyright (c) 2019 - present AppSeed.us
44
"""
5-
5+
import wtforms
66
from apps.home import blueprint
77
from flask import render_template, request, redirect, url_for
88
from jinja2 import TemplateNotFound
99
from flask_login import login_required, current_user
1010
from apps import db
11+
from apps.authentication.models import Users
12+
from flask_wtf import FlaskForm
1113

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

3335

36+
def getField(column):
37+
if isinstance(column.type, db.Text):
38+
return wtforms.TextAreaField(column.name.title())
39+
if isinstance(column.type, db.String):
40+
return wtforms.StringField(column.name.title())
41+
if isinstance(column.type, db.Boolean):
42+
return wtforms.BooleanField(column.name.title())
43+
if isinstance(column.type, db.Integer):
44+
return wtforms.IntegerField(column.name.title())
45+
if isinstance(column.type, db.Float):
46+
return wtforms.DecimalField(column.name.title())
47+
if isinstance(column.type, db.LargeBinary):
48+
return wtforms.HiddenField(column.name.title())
49+
return wtforms.StringField(column.name.title())
50+
51+
3452
@blueprint.route('/profile', methods=['GET', 'POST'])
3553
@login_required
3654
def profile():
37-
if request.method == 'POST':
38-
first_name = request.form.get('first_name')
39-
last_name = request.form.get('last_name')
40-
address = request.form.get('address')
41-
bio = request.form.get('bio')
42-
43-
current_user.first_name = first_name
44-
current_user.last_name = last_name
45-
current_user.address = address
46-
current_user.bio = bio
47-
48-
try:
49-
db.session.commit()
50-
except Exception as e:
51-
db.session.rollback()
5255

53-
return redirect(url_for('home_blueprint.profile'))
56+
class ProfileForm(FlaskForm):
57+
pass
58+
59+
readonly_fields = Users.readonly_fields
60+
full_width_fields = {"bio"}
61+
62+
for column in Users.__table__.columns:
63+
if column.name == "id":
64+
continue
5465

55-
return render_template('pages/profile.html', segment='profile')
66+
field_name = column.name
67+
if field_name in full_width_fields:
68+
continue
69+
70+
field = getField(column)
71+
setattr(ProfileForm, field_name, field)
72+
73+
for field_name in full_width_fields:
74+
if field_name in Users.__table__.columns:
75+
column = Users.__table__.columns[field_name]
76+
field = getField(column)
77+
setattr(ProfileForm, field_name, field)
78+
79+
form = ProfileForm(obj=current_user)
80+
81+
if form.validate_on_submit():
82+
readonly_fields.append("password")
83+
excluded_fields = readonly_fields
84+
for field_name, field_value in form.data.items():
85+
if field_name not in excluded_fields:
86+
setattr(current_user, field_name, field_value)
87+
88+
db.session.commit()
89+
return redirect(url_for('home_blueprint.profile'))
90+
91+
context = {
92+
'segment': 'profile',
93+
'form': form,
94+
'readonly_fields': readonly_fields,
95+
'full_width_fields': full_width_fields,
96+
}
97+
return render_template('pages/profile.html', **context)
5698

5799

58100
# Helper - Extract current page name from request

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@
66
"scripts": {
77
"dev": "vite build --watch --mode development",
88
"build": "vite build --mode production && npm run minify-css",
9-
"minify-css": "cssnano static/assets/css/*.css --dir static/assets/css --no-map --suffix .min"
9+
"minify-css": "postcss static/assets/css/*.css --dir static/assets/css --no-map --ext .min.css"
1010
},
1111
"keywords": [],
1212
"author": "",
1313
"license": "ISC",
1414
"devDependencies": {
1515
"autoprefixer": "^10.4.20",
1616
"cssnano": "^7.0.6",
17+
"postcss": "^8.5.3",
18+
"postcss-cli": "^11.0.0",
1719
"sass": "^1.85.1",
1820
"vite": "^6.2.0"
19-
},
20-
"dependencies": {
21-
"fast-glob": "^3.3.3"
2221
}
23-
}
22+
}

postcss.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
plugins: [
3+
require('cssnano')({
4+
preset: 'default',
5+
}),
6+
],
7+
};

run.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
app.logger.info('DEBUG = ' + str(DEBUG) )
5454
app.logger.info('Page Compression = ' + 'FALSE' if DEBUG else 'TRUE' )
5555
app.logger.info('DBMS = ' + app_config.SQLALCHEMY_DATABASE_URI)
56-
app.logger.info('ASSETS_ROOT = ' + app_config.ASSETS_ROOT )
5756

5857
if __name__ == "__main__":
5958
app.run()

templates/pages/profile.html

Lines changed: 23 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,56 +15,28 @@
1515
<h5>Edit Info</h5>
1616
</div>
1717
<div class="card-block px-4">
18-
<form class="row" method="POST" action="{{ url_for('home_blueprint.profile') }}">
19-
<div class="col-sm-6">
20-
<div class="form-group">
21-
<label for="exampleInputUsername">Username</label>
22-
<input class="form-control" id="exampleInputUsername" readonly
23-
value="{{ current_user.username }}" aria-describedby="userHelp" placeholder="Enter username">
24-
</div>
25-
</div>
26-
<div class="col-sm-6">
27-
<div class="form-group">
28-
<label {% if not current_user.email %} class="text-danger" {% endif %} for="exampleInputEmail1">Email address</label>
29-
<input type="email" class="form-control" id="exampleInputEmail1" readonly
30-
value="{{ current_user.email }}" aria-describedby="emailHelp" placeholder="Enter email">
31-
</div>
32-
</div>
33-
<div class="col-sm-6">
34-
<div class="form-group">
35-
<label for="fn">First Name</label>
36-
<input type="text" name="first_name" class="form-control" id="fn"
37-
value="{{ current_user.first_name or '' }}" placeholder="Your name">
38-
</div>
39-
</div>
40-
<div class="col-sm-6">
41-
<div class="form-group">
42-
<label for="ln">Last Name</label>
43-
<input type="text" name="last_name" class="form-control" id="ln"
44-
value="{{ current_user.last_name or '' }}" placeholder="Your last name">
45-
</div>
46-
</div>
47-
<div class="col-sm-12">
48-
<div class="form-group">
49-
<label for="add">Address</label>
50-
<input type="text" name="address" class="form-control" id="add"
51-
value="{{ current_user.address or '' }}" placeholder="Full address here">
52-
<small id="addd" class="form-text text-muted">This is your shipments address</small>
53-
</div>
54-
</div>
55-
<div class="col-sm-12">
56-
<div class="form-group">
57-
<label for="abt">About Info</label>
58-
<textarea name="bio" class="form-control" id="abt" placeholder="Bio">{{ current_user.bio or '' }}</textarea>
59-
<small id="abf" class="form-text text-muted">We'll show this on your profile.</small>
60-
</div>
61-
</div>
62-
<div class="col-sm-12 mb-2">
63-
<div class="form-group">
64-
<button type="submit" class="btn btn-primary">Submit</button>
65-
</div>
66-
</div>
67-
</form>
18+
<form class="row" method="POST">
19+
{{ form.hidden_tag() }}
20+
21+
{% for field in form %}
22+
{% if field.type in ['CSRFTokenField', 'HiddenField'] %}
23+
{{ field() }}
24+
{% else %}
25+
<div class="{% if field.name in full_width_fields %}col-sm-12{% else %}col-sm-6{% endif %}">
26+
<div class="form-group">
27+
<label for="" class="form-label">{{ field.name|replace_value("_") }} {% if field.name in readonly_fields %}(read-only){% endif %} </label>
28+
{{ field(class_="form-control", readonly=True if field.name in readonly_fields else False) }}
29+
</div>
30+
</div>
31+
{% endif %}
32+
{% endfor %}
33+
34+
<div class="col-sm-12 mb-2">
35+
<div class="form-group">
36+
<button type="submit" class="btn btn-primary">Submit</button>
37+
</div>
38+
</div>
39+
</form>
6840
</div>
6941
</div>
7042
</div>
@@ -73,7 +45,7 @@ <h5>Edit Info</h5>
7345
<div class="card-block">
7446
<div class="d-flex align-items-center justify-content-center flex-column">
7547
<div class="w-50 p-3">
76-
<img src="{{ config.ASSETS_ROOT }}/images/user/profile.jpg" alt="profile image"
48+
<img src="{{ url_for('static', filename='assets/img/bruce-mars.jpg') }}" alt="profile image"
7749
class="img-fluid rounded-circle">
7850
</div>
7951
<div class="text-center">

0 commit comments

Comments
 (0)