Is it bad practice to have multiple routes per flask view function? - python

So basically I have multiple render_template returns based on if statements, and they return different variables that my jinja2 template responds to. I believe I could break up these routes into their own functions (likewise I could break up my templates too into more than one template (for example an edit.html template instead of an {% if editform %} in my template)), but I like the idea of having a single view function and template for any given page.
Before I spend more time creating the rest of my view functions, I want to make sure that what I'm doing isn't going to bite me later.
Code below, thanks!
#app.route('/admin/users/', defaults={'id': None}, methods = ['GET'])
#app.route('/admin/users/<id>', methods = ['GET'])
#app.route('/admin/users/edit/<id>', methods = ['GET', 'POST'])
#login_required
def users(id):
if not current_user.role == ROLE_ADMIN:
flash('You do not have access to view this page.')
return redirect(url_for('index'))
if id:
user = User.query.filter_by(id = id).first()
if 'edit' in request.path:
editform = UserForm()
if editform.validate_on_submit():
user.username = editform.username.data
user.email = editform.email.data
user.role = editform.role.data
db.session.add(user)
db.session.commit()
return redirect('/admin/users/' + str(user.id))
editform.username.data = user.username
editform.email.data = user.email
editform.role.data = user.role
return render_template("users.html",
title = "Edit User",
user = user,
editform = editform)
return render_template("users.html",
title = "User",
user = user)
users = User.query.all()
return render_template("users.html",
title = 'Users',
users = users)

The answer to this question is probably a little bit down to personal taste, but personally I'd avoid doing things like this. My main reason would just be to keep things simple - you've got three nested if statements inside that function, that are basically doing the exact same job as flasks routing logic would do if you were to split up the views.
Admittedly it's not a massive problem at present, but if you were to add more logic in to any of the paths then it could lead to a surprisingly complex function. This could lead to subtle bugs, or just plain difficulty understanding exactly what's going on from a glance.
If you split things up however, it should be obvious at a glance exactly what each route is doing. Doesn't even involve more code, since you can cut out on all the if statements:
#app.route('/admin/users/', methods = ['GET'])
def get_all_users():
users = User.query.all()
return render_template("users.html",
title = 'Users',
users = users)
#app.route('/admin/users/<id>', methods = ['GET'])
def get_single_user(id):
user = User.query.filter_by(id = id).first()
return render_template("users.html",
title = "User",
user = user)
#app.route('/admin/users/edit/<id>', methods = ['GET', 'POST'])
def edit_user(id):
editform = UserForm()
if editform.validate_on_submit():
user.username = editform.username.data
user.email = editform.email.data
user.role = editform.role.data
db.session.add(user)
db.session.commit()
return redirect('/admin/users/' + str(user.id))
editform.username.data = user.username
editform.email.data = user.email
editform.role.data = user.role
return render_template("users.html",
title = "Edit User",
user = user,
editform = editform)
EDIT: To clarify - I'm not saying it's wrong to have more than one url per flask route. In this case I think it's a bad idea to have a single function doing three fairly different things. There are other use cases for multiple routes per view that I do make use of. For example, in my own code I frequently have views like this:
#migrations_tasks.route('/external_applications', methods=['GET'])
#migrations_tasks.route('/external_applications/<cursor>', methods=['POST'])
def migrate_external_applications(cursor=None, subdomain=None):
... do stuff
Where the alternative route accepts a cursor into a database query for pagination. The flow of the code is basically the same, it just has different URLs for the first page and subsequent pages.

There is no hard and fast rule, but I would suggest splitting into multiple functions is more modular going with the Separation of concerns principle.
I would even go one step further and use the MethodView, where there is a class per route with get,post, post etc overridden from base.
When your function is small it looks ok, but when it grows some modularity helps !

Related

Pass a variable to another function and render an html template at the same time in django

We want to access the same variable in every function inside our views.py. Since it is not constant, we cannot use it as a global variable.
Is it possible to pass a variable to another function while also rendering an HTML template? What are the alternatives if none exist?
This is our login function in views.py
def loginpage(request):
errorMessage = ''
# Applicant Login
if request.method=="POST":
if request.POST.get('username') and request.POST.get('pwd'):
try:
currentUser=Applicant.objects.get(username=request.POST['username'],pwd=request.POST['pwd'])
currentUser=Applicant.objects.get(username=request.POST['username'])
first = currentUser.firstname
middle = currentUser.middleinitial
last = currentUser.lastname
AppDashboard = ApplicantDashboardPageView(currentUser, request)
except Applicant.DoesNotExist as e:
errorMessage = 'Invalid username/password!'
return render(request, 'home.html')
The currentUser variable inside our login function is the variable we want to pass in this function
def ApplicantdashboardPageView(currentUser, request):
appPeriod = ApplicationPeriod.objects.all()
exam = ExaminationSchedule.objects.all()
posts = Post.objects.all().order_by('-created_on')
form = PostForm()
name=userNaCurrent
print('from storeCurrentUser', name)
if request.method == "GET":
try:
posts = Post.objects.all().order_by('-created_on')
form = PostForm()
#applicantID=currentUser.id
#applicantNotification = Applicant.objects.get(id=applicantID)
return render(request, 'applicantdashboard.html', context={'UserName' : name, 'posts':posts, 'appPeriod':appPeriod, 'exam':exam})
except ObjectDoesNotExist:
return render(request, 'applicantdashboard.html', context={'UserName' : name, 'posts':posts,})
return render(request, 'applicantdashboard.html', context={'UserName' : name, 'posts':posts, 'appPeriod':appPeriod, 'exam':exam})
I am new to Django so please bear with me if my question seem too basic. Thank you
Store raw user password is a very big flaw in security. Please read more about Django Authentication system https://docs.djangoproject.com/en/4.1/topics/auth/
Basically, to store critical confidential information like passwords you need to at least, encrypt it. But for passwords you don't need to see the raw value of it, isn't it? Therefore, you just need to hash it and compare it every time you need to authenticate the user. Read more here Best way to store password in database
Django Auth system will also help to solve the issue by injecting the current user into a "global" request object so that you can access it everywhere.
You can do the same by keeping those 2 methods in a class and accessing variables by creating objects for it.

How to display username in multiple pages using flask?

I have encountered a problem of having the username not being shown on all the pages running under my flask application, it only runs at the home page because I return it from the login function. However, I don't know how could I get all the pages being called to have the username visible in all of them.
In the code attached, the def featured_books() doesn't return the username as it should be, instead it returns it as 1. How to solve this issue?
flask.py
#app.route('/')
# Gets correct username
#app.route('/index')
def home(post=1):
return render_template("index.html", username=post)
# Doesn't get the username
#app.route('/featured_books')
def featured_books(post=1):
return render_template("featured_books.html", username=post)
#app.route('/login')
def login(post=1):
if not session.get('logged_in'):
return render_template("login.html")
else:
return home(post)
#app.route('/login', methods=['POST'])
def do_login():
POST_USERNAME = str(request.form['username'])
POST_PASSWORD = str(request.form['password'])
secure_password = sha512_crypt.encrypt(POST_PASSWORD)
Session = sessionmaker(bind=engine)
s = Session()
query = s.query(User_reg).filter(User_reg.username.in_([POST_USERNAME]),
User_reg.password.in_([POST_PASSWORD]))
result = query.first()
if result:
session['logged_in'] = True
else:
flash('Wrong password!')
return login(POST_USERNAME)
I was working on the same thing and came across your question.
Have you figured it out?
Some suggest using Flask-Login extension to handle session management. Because I'm working on a simple app I decided to put the user name into a session cookie. Then can just grab it from the session on another page. Seemed like the path of least resistance, though, may not be the best choice.
Storing it in route.py
session['user_name'] = user.firstname + ' ' + user.lastname
Grabbing it from an html page
<h3> Welcome, {{session['user_name']}}! </h3>

Update global dictionary/list from within an #app.route in Flask

I'm trying to find a way to update a dictionary (global) from within a function in a flask app.
I want to store information about users that create an account in a dictionary like this.
user {
'key': {'key1':'value1', 'key2':'value2'}
}
But I can't seem to be able to update the dict or list from within the function.
I've tried both
user['key']={'key1':'value1', 'key2':'value2'}
and
user.update({'key': {'key1':'value1', 'key2':'value2'}
methods of updating but it's not working.
This is the full code of this portion.
channel_list = []
users = {}
#app.route("/", methods=["GET", "POST"])
def index():
username = request.form.get("username")
firstname = request.form.get("firstname")
lastname = request.form.get("lastname")
if request.method == "GET":
if session.get("user_name"):
user_name = session.get("user_name")
get_user = users[user_name]
render_template("profile.html", user_name=get_user, users=users)
else:
return render_template("index.html")
if request.method == "POST":
session["user_name"] = username
newuser = {username: {"username": username, "firstname": firstname, "lastname": lastname}}
users.update(newuser)
get_user = users[username]
return render_template("profile.html", user=get_user, users=users)
#app.route("/signin", methods=["GET", "POST"])
def signin():
username = request.form.get("username")
if request.method == "GET":
if session.get("user_name"):
user_name = session.get("user_name")
get_user = users[user_name]
render_template("profile.html", user=get_user, users=users)
else:
return render_template("signin.html")
if request.method == "POST":
session["user_name"] = username
get_user = users[username]
return render_template("profile.html", user=get_user, users=users)
return render_template("signin.html")
I've figured out the problem from trying this in pure python and there, I'm also not able to update the dict without first running the function. But, how do I run each individual function (ex, index() or signin()) in flask, because I think this is the problem? I think this should be taken care of by app.run() but it's not working.
if __name__ == "__main__":
app.run()
socketio.run(app)
I keep getting "KeyError" each time because nothing is being inserted in the dict so there's nothing to select or access.
I'm trying to avoid using a DB and I really want to figure this out, I don't know what I'm doing wrong. The code works really well outside the flask app which is why it's frustrating.
The key error is being generated here due to this line:
if request.method == "GET":
...
get_user = users[user_name]
This is because when you start your Flask application, initially the 'users' dictionary is empty. So when you access either of the two routes for the first time using a 'GET' method (which is the default method when you type the URL into a browser), this line here would generate a KeyError as the dictionary is empty and the key "user_name" does not exist. A common way to fix this would be to use a try-except block and handle the Exception.
Also, both of the two functions seem to be doing the same thing, i.e. signing the user in. It would be better if you distinguished the two functions properly. I would recommend keeping all the sign-in logic in the 'signin' route and using the 'index' route to simply display the login page. You should also set the methods of the two routes properly. Only the 'signin' function is supposed to be accessed via a POST method.

How to mock databases access while testing urls with Django

I want to test some django application urls. However, the associated views are linked to the database. What I'd like to do is mocking these aspects of the view method, but I have no idea how.
Let's suppose I want to try the /signin url, whici is a classical signin form.
The associated view looks like this :
def login(request):
if 'user' in request.session:
return redirect(reverse("home"))
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
return treat_login(request, username, password) # checks if couple
is present in
database, returns
pages accordingly
else:
form = LoginForm()
return render(request, 'login.html', {'form':form, })
In my test, I have no implicit call to the login method, since I only use the url :
class Tests_urls(TestCase):
def test_signin(self):
self.client.post(reverse("login"), {"username":"login", "password":"pwd"})
self.assert_http_status(url, status, "after a standard login")
The problem with that test is that it needs a database to be performed, wich is what I want to avoid (I can't use use the embedded test database).
As a consequence, I would like to know how to mock the treat_login method from the test point of view.
You can use patch from the mock libarary
from mock import patch
class Tests_urls(TestCase):
#patch('my_app.views.treat_login')
def test_signin(self, mock_treat_login):
self.client.post(reverse("login"), {"username":"login", "password":"pwd"})
self.assert_http_status(url, status, "after a standard login")
self.assertTrue(mock_treat_login.called)
You can also inspect the call args. But the way you have written this test makes that a bit hard. If you used the request factory and tested the function by doing something like
request = self.factory.post(
reverse("login"), {"username":"login", "password":"pwd"})
response = login(request
mock_treat_login.assert_called_once_with(request, "login", "pwd)
Then you could actually make sure you were calling it correctly.

Unit testing Flask function (with logged in user)

I'm new to Python and Flask but I'm gradually getting to grips with it. I've got so far into building an app and I'm now thinking I should start some unit testing. I really can't get my head around it though. I've read various docs, posts and examples but I can't seem to transfer this to my own code. I'm hoping if someone can show me how to write a test for one of my functions then things will fall into place. It's very tempting at this stage to ignore it and press on building my app.
#app.route('/user/<nickname>/create_bike', methods = ['GET', 'POST'] )
#login_required
def create_bike(nickname):
user = User.query.filter_by(nickname = nickname).first()
bikes = user.bikes.all()
bikecount = user.bikes.count()
form = CreateBike()
if form.validate_on_submit():
if bikecount < user.bike_allowance:
# let user create a bike
newbike = Bikes(bikename = form.bikename.data,
user_id = g.user.id, shared = SHARED )
db.session.add(newbike)
db.session.commit()
flash('Your new bike has been created')
return redirect(url_for('create_bike', nickname = nickname))
else:
flash("You have too many bikes")
return render_template('create_bike.html',
user = user,
form = form
)
UPDATE - Here's my working test
def test_create_bike(self):
u = User(nickname = 'john', email = 'john#example.com', account_type = "tester")
db.session.add(u)
db.session.commit()
# login user
with self.app as c:
with c.session_transaction() as sess:
sess['user_id'] = int(u.get_id())
# http://pythonhosted.org/Flask-Login/#fresh-logins
sess['_fresh'] = True
rv = c.post('/user/john/create_bike', data = dict(
bikename = 'your new bike',
user_id = sess['user_id'],
shared = 1
), follow_redirects = True)
assert 'Your new bike has been created' in rv.data
Your test is likely failing because that view requires a logged in user, and since you arent passing in any session data, you are being redirected to the login page (the data argument to .post is form data, available in request.form in your view). Before your assertion you can see what the response is to help you along the way:
print rv.status_code
print rv.location #None if not a redirect
There's some documentation around sessions in tests here, and if you're using Flask-Login like it looks you are, this answer shows you how to set the session up so you get a logged in user
You're on the right track.
Checking if the html page contains some piece of data that should be there is one the most common scenarios in testing web apps. You just need to repeat for all the other pages that you create. You can also add tests to see that creating a user follows all the validation rules that you've setup or that the username field is unique etc.

Categories