mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-27 18:57:17 +00:00
Updated Server (#1630)
This commit is contained in:
+16
-21
@@ -1,35 +1,30 @@
|
|||||||
# Server-Test
|
# Server-Test
|
||||||
|
|
||||||
## Set up the server by installing the dependencies
|
1. Cd to the server directory i.e cd `IfcOpenShell\src\bcfserver`
|
||||||
|
|
||||||
### run `pip install -r requirements.txt` to install the dependencies
|
2. Set up the server by installing the dependencies
|
||||||
|
|
||||||
#### setup the database by running `db.create_all()` in python shell by importing db from website
|
3. Run `pip install -r requirements.txt` to install the dependencies
|
||||||
|
|
||||||
### run `set FLASK_APP=app.py` to set the environment variable
|
4. In Python Shell, do the following
|
||||||
|
|
||||||
### run `flask run` to start the server
|
- from run import db
|
||||||
|
- db.create_all() to setup the database table
|
||||||
|
|
||||||
### Go to [http://localhost:5000](http://localhost:5000) to see the server
|
5. Run `set FLASK_APP=run.py`
|
||||||
|
6. Run `flask run` to start the server
|
||||||
|
7. Go to [http://localhost:5000](http://localhost:5000) to see the server
|
||||||
|
|
||||||
# Register the user
|
# Register the user
|
||||||
|
|
||||||
#### Go to [http://localhost:5000/register](http://localhost:5000/register) to register the user
|
1. Go to http://localhost:5000/register to register the user
|
||||||
|
2. Create the client
|
||||||
# Create the client
|
3. For grant type enter authorization_code
|
||||||
|
4. For response_type enter code secret
|
||||||
#### For `grant type` enter `authorization_code`
|
5. Enter the scope and create the client
|
||||||
|
|
||||||
#### For `response_type` enter `code secret`
|
|
||||||
|
|
||||||
### Enter the scope and create the client
|
|
||||||
|
|
||||||
### You will be redirected to the page with the details of your client id and secret
|
### You will be redirected to the page with the details of your client id and secret
|
||||||
|
|
||||||
### Use the bcf/v3/api.py to get the access token
|
# Foundation API
|
||||||
|
|
||||||
#### For authentication endpoint use `http://localhost:5000/oauth/authorize`
|
- Set the Base URL will be `http://127.0.0.1:5000/`
|
||||||
|
|
||||||
### For token endpoint use `http://localhost:5000/oauth/token`
|
|
||||||
|
|
||||||
### Base URL will be `http://localhost:5000/`
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
from website import app
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run(debug=True)
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
from flask import jsonify, url_for, redirect, render_template, request, session, flash
|
||||||
|
from flask_login import login_user, logout_user, login_required, current_user
|
||||||
|
from flask.blueprints import Blueprint
|
||||||
|
from website.models import User, OAuth2AuthorizationCode, OAuth2Token, OAuth2Client
|
||||||
|
from run import app, db
|
||||||
|
import json
|
||||||
|
|
||||||
|
bcf = Blueprint("bcf", __name__, template_folder="templates", url_prefix="/bcf/3.0")
|
||||||
|
|
||||||
|
|
||||||
|
@bcf.route("/projects")
|
||||||
|
def projects():
|
||||||
|
Headers = str.split(request.headers["Authorization"])
|
||||||
|
token = Headers[1]
|
||||||
|
access_token = OAuth2Token.query.filter_by(access_token=token).first()
|
||||||
|
print(access_token)
|
||||||
|
if access_token:
|
||||||
|
Body = {
|
||||||
|
"project_id": "F445F4F2-4D02-4B2A-B612-5E456BEF9137",
|
||||||
|
"name": "Example project 1",
|
||||||
|
"authorization": {"project_actions": ["createTopic", "createDocument"]},
|
||||||
|
}, {
|
||||||
|
"project_id": "A233FBB2-3A3B-EFF4-C123-DE22ABC8414",
|
||||||
|
"name": "Example project 2",
|
||||||
|
"authorization": {"project_actions": []},
|
||||||
|
}
|
||||||
|
response = app.response_class(
|
||||||
|
response=json.dumps(Body),
|
||||||
|
status=200,
|
||||||
|
mimetype="application/json",
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
message = {"error": "User not recognized"}
|
||||||
|
response = app.response_class(
|
||||||
|
response=jsonify(message),
|
||||||
|
status=200,
|
||||||
|
mimetype="application/json",
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@bcf.route("/")
|
||||||
|
@login_required
|
||||||
|
def bcf_3():
|
||||||
|
return "<h1>BCF HOMPAGE</h1>"
|
||||||
|
|
||||||
|
|
||||||
|
@bcf.route("/projects/<project_id>")
|
||||||
|
def project_details(project_id):
|
||||||
|
return "Project details"
|
||||||
@@ -1,25 +1,8 @@
|
|||||||
appdirs
|
Authlib==0.15.4
|
||||||
bcrypt
|
email-validator==1.1.3
|
||||||
black
|
Flask==2.0.1
|
||||||
cffi
|
Flask-Login==0.5.0
|
||||||
click
|
Flask-SQLAlchemy==2.5.1
|
||||||
colorama
|
Flask-WTF==0.15.1
|
||||||
dnspython
|
Flask-Bcrypt==0.7.1
|
||||||
email-validator
|
bcrypt==3.2.0
|
||||||
Flask
|
|
||||||
Flask-Login
|
|
||||||
Flask-WTF1
|
|
||||||
greenlet
|
|
||||||
idnatsdangerous
|
|
||||||
Jinja
|
|
||||||
MarkupSafe
|
|
||||||
mypy-extensions
|
|
||||||
pathspec
|
|
||||||
pycparserregex
|
|
||||||
six
|
|
||||||
SQLAlchemy
|
|
||||||
tomli
|
|
||||||
Werkzeug
|
|
||||||
WTForms
|
|
||||||
authlib
|
|
||||||
Flask-RESTful
|
|
||||||
|
|||||||
@@ -3,15 +3,23 @@ from flask_sqlalchemy import SQLAlchemy
|
|||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
login_manager = LoginManager(app)
|
login_manager = LoginManager(app)
|
||||||
bcrypt = Bcrypt(app)
|
bcrypt = Bcrypt(app)
|
||||||
app.config["SECRET_KEY"] = "f613729206685405cde0e388"
|
app.config["SECRET_KEY"] = "f613729206685405cde0e388"
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db"
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db"
|
||||||
login_manager.login_view = "login_page"
|
login_manager.login_view = "website_obj.login_page"
|
||||||
login_manager.login_message_category = "info"
|
login_manager.login_message_category = "info"
|
||||||
|
|
||||||
|
from website.routes import website_obj
|
||||||
|
from bcf.routes import bcf
|
||||||
|
|
||||||
from website import models, oauth2, routes
|
|
||||||
|
|
||||||
|
app.register_blueprint(website_obj)
|
||||||
|
app.register_blueprint(bcf)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||||
@@ -2,7 +2,7 @@ from flask_wtf import FlaskForm
|
|||||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
||||||
from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo
|
from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo
|
||||||
from wtforms import ValidationError
|
from wtforms import ValidationError
|
||||||
from website.models import User
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
class RegisterForm(FlaskForm):
|
class RegisterForm(FlaskForm):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from website import db, bcrypt, login_manager
|
from run import db, bcrypt, login_manager
|
||||||
import time
|
import time
|
||||||
from authlib.integrations.sqla_oauth2 import (
|
from authlib.integrations.sqla_oauth2 import (
|
||||||
OAuth2ClientMixin,
|
OAuth2ClientMixin,
|
||||||
|
|||||||
@@ -1,28 +1,34 @@
|
|||||||
from os import supports_bytes_environ
|
from flask.blueprints import Blueprint
|
||||||
from website import app
|
from flask import render_template, redirect, url_for, flash, request
|
||||||
from flask import render_template, redirect, url_for, flash, request, jsonify
|
from werkzeug import datastructures
|
||||||
from website.models import User
|
from .forms import RegisterForm, LoginForm, OauthForm
|
||||||
from website.forms import RegisterForm, LoginForm, OauthForm
|
|
||||||
from website import db
|
|
||||||
from flask_login import login_user, logout_user, login_required, current_user
|
from flask_login import login_user, logout_user, login_required, current_user
|
||||||
from .models import db, User, OAuth2Client, OAuth2AuthorizationCode, OAuth2Token
|
from .models import User, OAuth2Client, OAuth2AuthorizationCode, OAuth2Token
|
||||||
import base64
|
import base64
|
||||||
from werkzeug.security import gen_salt
|
from werkzeug.security import gen_salt
|
||||||
import time
|
import time
|
||||||
import urllib
|
import urllib
|
||||||
import json
|
import json
|
||||||
|
from run import db, app
|
||||||
|
|
||||||
|
website_obj = Blueprint(
|
||||||
|
"website_obj",
|
||||||
|
__name__,
|
||||||
|
template_folder="templates",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def split_by_crlf(s):
|
def split_by_crlf(s):
|
||||||
return [v for v in s.splitlines() if v]
|
return [v for v in s.splitlines() if v]
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@website_obj.route("/")
|
||||||
def homepage():
|
def homepage():
|
||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
|
# return "homepage"
|
||||||
|
|
||||||
|
|
||||||
@app.route("/register", methods=["GET", "POST"])
|
@website_obj.route("/register", methods=["GET", "POST"])
|
||||||
def register_page():
|
def register_page():
|
||||||
form = RegisterForm()
|
form = RegisterForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
@@ -39,7 +45,7 @@ def register_page():
|
|||||||
f"Account created successfully! {user_to_create.username}",
|
f"Account created successfully! {user_to_create.username}",
|
||||||
category="success",
|
category="success",
|
||||||
)
|
)
|
||||||
return redirect(url_for("homepage"))
|
return redirect(url_for("website_obj.homepage"))
|
||||||
if form.errors != {}:
|
if form.errors != {}:
|
||||||
for err_msg in form.errors.values():
|
for err_msg in form.errors.values():
|
||||||
flash(
|
flash(
|
||||||
@@ -49,7 +55,7 @@ def register_page():
|
|||||||
return render_template("register.html", form=form)
|
return render_template("register.html", form=form)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/createclient", methods=["GET", "POST"])
|
@website_obj.route("/createclient", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def create_client():
|
def create_client():
|
||||||
grants = [
|
grants = [
|
||||||
@@ -93,7 +99,7 @@ def create_client():
|
|||||||
return render_template("createclient.html", form=form, grants=grants)
|
return render_template("createclient.html", form=form, grants=grants)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/login", methods=["GET", "POST"])
|
@website_obj.route("/login", methods=["GET", "POST"])
|
||||||
def login_page():
|
def login_page():
|
||||||
form = LoginForm()
|
form = LoginForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
@@ -102,7 +108,7 @@ def login_page():
|
|||||||
attempted_password=form.password.data
|
attempted_password=form.password.data
|
||||||
):
|
):
|
||||||
login_user(attempted_user)
|
login_user(attempted_user)
|
||||||
return redirect(url_for("homepage"))
|
return redirect(url_for("website_obj.homepage"))
|
||||||
else:
|
else:
|
||||||
flash(
|
flash(
|
||||||
"Invalid Credentials",
|
"Invalid Credentials",
|
||||||
@@ -112,27 +118,27 @@ def login_page():
|
|||||||
return render_template("login.html", form=form)
|
return render_template("login.html", form=form)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/logout")
|
@website_obj.route("/logout")
|
||||||
def logoutpage():
|
def logoutpage():
|
||||||
logout_user()
|
logout_user()
|
||||||
flash("You have been logged out!", category="info")
|
flash("You have been logged out!", category="info")
|
||||||
return redirect(url_for("homepage"))
|
return redirect(url_for("website_obj.homepage"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/foundation/1.0/auth")
|
@website_obj.route("/foundation/1.0/auth")
|
||||||
def foundation_auth():
|
def foundation_auth():
|
||||||
data = {
|
data = {
|
||||||
"oauth2_auth_url": "http://127.0.0.1:5000/oauth/authorize",
|
"oauth2_auth_url": "http://127.0.0.1:5000/oauth/authorize",
|
||||||
"oauth2_token_url": "http://127.0.0.1:5000/oauth/token",
|
"oauth2_token_url": "http://127.0.0.1:5000/oauth/token",
|
||||||
"supported_oauth2_flows": ["authorization_code"],
|
"supported_oauth2_flows": ["authorization_code"],
|
||||||
}
|
}
|
||||||
response = app.response_class(
|
response = website_obj.response_class(
|
||||||
response=json.dumps(data), status=200, mimetype="application/json"
|
data=json.dumps(data), status=200, mimetype="application/json"
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@app.route("/foundation/versions")
|
@website_obj.route("/foundation/versions")
|
||||||
def foundation_versions():
|
def foundation_versions():
|
||||||
Body = {
|
Body = {
|
||||||
"versions": [
|
"versions": [
|
||||||
@@ -155,13 +161,13 @@ def foundation_versions():
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
response = app.response_class(
|
response = website_obj.response_class(
|
||||||
response=json.dumps(Body), status=200, mimetype="application/json"
|
data=json.dumps(Body), status=200, mimetype="application/json"
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@app.route("/outh/login", methods=["GET", "POST"])
|
@website_obj.route("/outh/login", methods=["GET", "POST"])
|
||||||
def oauth_login():
|
def oauth_login():
|
||||||
form = LoginForm()
|
form = LoginForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
@@ -179,7 +185,7 @@ def oauth_login():
|
|||||||
state = request.args.get("state")
|
state = request.args.get("state")
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"authorize",
|
"website_obj.authorize",
|
||||||
client_id=client_id,
|
client_id=client_id,
|
||||||
redirect_uri=redirect_uri,
|
redirect_uri=redirect_uri,
|
||||||
state=state,
|
state=state,
|
||||||
@@ -193,7 +199,7 @@ def oauth_login():
|
|||||||
return render_template("ologin.html", form=form)
|
return render_template("ologin.html", form=form)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/oauth/authorize", methods=["GET", "POST"])
|
@website_obj.route("/oauth/authorize", methods=["GET", "POST"])
|
||||||
def authorize():
|
def authorize():
|
||||||
client_id = request.args.get("client_id")
|
client_id = request.args.get("client_id")
|
||||||
redirect_uri = request.args.get("redirect_uri")
|
redirect_uri = request.args.get("redirect_uri")
|
||||||
@@ -203,7 +209,7 @@ def authorize():
|
|||||||
query = request.query_string
|
query = request.query_string
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"oauth_login",
|
"website_obj.oauth_login",
|
||||||
client_id=client_id,
|
client_id=client_id,
|
||||||
redirect_uri=redirect_uri,
|
redirect_uri=redirect_uri,
|
||||||
state=state,
|
state=state,
|
||||||
@@ -246,7 +252,7 @@ def authorize():
|
|||||||
return render_template("oauth.html")
|
return render_template("oauth.html")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/oauth/token", methods=["POST"])
|
@website_obj.route("/oauth/token", methods=["POST"])
|
||||||
def issue_token():
|
def issue_token():
|
||||||
try:
|
try:
|
||||||
code = request.form["code"]
|
code = request.form["code"]
|
||||||
@@ -254,6 +260,7 @@ def issue_token():
|
|||||||
decode_header = base64.b64decode(Headers[1]).decode("utf-8")
|
decode_header = base64.b64decode(Headers[1]).decode("utf-8")
|
||||||
creds = decode_header.split(":")
|
creds = decode_header.split(":")
|
||||||
client_id = creds[0]
|
client_id = creds[0]
|
||||||
|
# print(code, Headers, decode_header, creds, client_id)
|
||||||
auth_code = OAuth2AuthorizationCode.query.filter_by(code=code).first()
|
auth_code = OAuth2AuthorizationCode.query.filter_by(code=code).first()
|
||||||
if auth_code:
|
if auth_code:
|
||||||
if auth_code.client_id == client_id:
|
if auth_code.client_id == client_id:
|
||||||
@@ -275,11 +282,13 @@ def issue_token():
|
|||||||
"access_token": access_token,
|
"access_token": access_token,
|
||||||
"refresh_token": refresh_token,
|
"refresh_token": refresh_token,
|
||||||
"expires_in": expires_in,
|
"expires_in": expires_in,
|
||||||
"auth_code": auth_code.scope,
|
|
||||||
}
|
}
|
||||||
response = app.response_class(
|
response = app.response_class(
|
||||||
response=json.dumps(query), status=200, mimetype="application/json"
|
response=json.dumps(query),
|
||||||
|
status=200,
|
||||||
|
mimetype="application/json",
|
||||||
)
|
)
|
||||||
|
print(query)
|
||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
flash(
|
flash(
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<ul class="navbar-nav mr-auto">
|
<ul class="navbar-nav mr-auto">
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="{{ url_for('homepage') }}"
|
<a class="nav-link" href="{{ url_for('website_obj.homepage') }}"
|
||||||
>Home <span class="sr-only">(current)</span></a
|
>Home <span class="sr-only">(current)</span></a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
@@ -47,10 +47,14 @@
|
|||||||
<a class="nav-link">Welcome, {{ current_user.username }}</a>
|
<a class="nav-link">Welcome, {{ current_user.username }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('logoutpage') }}">Logout</a>
|
<a class="nav-link" href="{{ url_for('website_obj.logoutpage') }}"
|
||||||
|
>Logout</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('create_client') }}"
|
<a
|
||||||
|
class="nav-link"
|
||||||
|
href="{{ url_for('website_obj.create_client') }}"
|
||||||
>Create Client</a
|
>Create Client</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
@@ -58,10 +62,14 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('login_page') }}">Login</a>
|
<a class="nav-link" href="{{ url_for('website_obj.login_page') }}"
|
||||||
|
>Login</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('register_page') }}"
|
<a
|
||||||
|
class="nav-link"
|
||||||
|
href="{{ url_for('website_obj.register_page') }}"
|
||||||
>Register</a
|
>Register</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
{%extends 'base.html'%} {%block title%} Homepage {%endblock%} {%block content%}
|
<!-- {%extends 'base.html'%} {%block title%} Homepage {%endblock%} {%block content%}
|
||||||
<h1>BCF Open Source Server Homepage</h1>
|
<h1>BCF Open Source Server Homepage</h1>
|
||||||
{%endblock%}
|
{%endblock%} -->
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Home Page
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<h6>Do not have an account?</h6>
|
<h6>Do not have an account?</h6>
|
||||||
<a
|
<a
|
||||||
class="btn btn-sm btn-secondary"
|
class="btn btn-sm btn-secondary"
|
||||||
href="{{ url_for('register_page') }}"
|
href="{{ url_for('website_obj.register_page') }}"
|
||||||
>Register</a
|
>Register</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<h6>Do not have an account?</h6>
|
<h6>Do not have an account?</h6>
|
||||||
<a
|
<a
|
||||||
class="btn btn-sm btn-secondary"
|
class="btn btn-sm btn-secondary"
|
||||||
href="{{ url_for('register_page') }}"
|
href="{{ url_for('website_obj.register_page') }}"
|
||||||
>Register</a
|
>Register</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,9 @@
|
|||||||
<br />
|
<br />
|
||||||
<div class="checkbox mb-3">
|
<div class="checkbox mb-3">
|
||||||
<h6>Already have an account?</h6>
|
<h6>Already have an account?</h6>
|
||||||
<a class="btn btn-sm btn-secondary" href="{{ url_for('login_page') }}"
|
<a
|
||||||
|
class="btn btn-sm btn-secondary"
|
||||||
|
href="{{ url_for('website_obj.login_page') }}"
|
||||||
>Login</a
|
>Login</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user