How to minimise number of routes in my Flask webapp - python

I am attempting to minimize the number of routes and database tables in my simple clothing ecommerce site. My site has four product categories: Coat, Shirt, Trouser, Shoe.
Each category currently has its own table in the database:
class Coat(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False)
description = db.Column(db.Text, nullable=True)
price = db.Column(db.Float)
image1 = db.Column(db.String(100), nullable=True, default='default.jpg')
image2 = db.Column(db.String(100), nullable=True)
I would like to combine all products into a single database with 'Category' being a column inside this Product database.
Each category also currently has its own route and template:
#app.route('/coats')
def coats():
products = Coat.query.all()
return render_template('coats.html', products=products, title='Coats')
{% extends "base.html" %}
{% block content %}
{% for product in products %}
<div class="product">
<a href="{{ url_for('product_coat', id=product.id) }}">
<img src="static/images/coats/{{ product.image1 }}" class="image"></a>
<p class="name">{{ product.name }}</p>
<p class="price">{{ product.price }}</p>
</div>
{% endfor %}
{% endblock %}
When a products link is followed, a product page is displayed, again using individual routes for each category:
#app.route('/product_coat/<id>', methods=['GET', 'POST'])
def product_coat(id):
product = Coat.query.get(id)
return render_template('product_coat.html', title='Product',
product=product)
Currently my navbar in "base.html" looks like this:
<nav>
<h3>Coats</h3>
<h3>Shirts</h3>
<h3>Trousers</h3>
<h3>Shoes</h3>
</nav>
I realise this is a rather large post so to summarise,
If I were to combine all four database tables into one product table with a new 'category' column, how would I:
Create navbar links for these categories?
Create a route that handles displaying only products from the selected category?
Thanks for reading.

You can pass query parameters to a new route,
#app.route('/product/', methods=['GET'])
def product_list(id):
item = request.args.get('item')
product = products.all.filter(type=item)
return render_template(f"{item}.html", title=item,product=product)
then when you load the page, you can call url/?item=coat and it will filter the data.
*that product filter line might be incorrect.

What you can do is attempt to follow a more RESTful design pattern.
The main idea is to add a parameter to your routes to represent the category. This would be similar to what you've done for the product ID. Importantly, you would have only one HTML template for the products page (the page that lists all the products of a particular category) and one HTML template for the product page (the page for listing the details of one product of a particular category).
So your two existing routes could become something like this:
(Note your logic should ensure an invalid product category and/or ID is correctly handled)
#app.route('/products/<product_category>/<product_id>', methods=['GET', 'POST'])
def product(product_category, product_id):
product = <your database logic utilizing the product_category and product_id + error handling>
return render_template('product.html', product=product, title=product.name)
#app.route('/products/<product_category>', methods=['GET'])
def products(product_category):
products = <your database logic utilizing the product_category + error handling>
return render_template('products.html', products=products, title=product_category.title())
Your navbar HTML could end up looking like this:
(Here we specify the 2nd route and supply just the category)
<nav>
<h3>Coats</h3>
<h3>Shirts</h3>
<h3>Trousers</h3>
<h3>Shoes</h3>
</nav>
Finally, your product display HTML could end up like this:
(Here we specify the 1st route and supply the category and product ID -- since we're linking to a specific product in this case)
<div class="product">
<a href="{{ url_for('product', product_category=product.category, product_id=product.id) }}">
<img src="{{ url_for('static', filename='images/coats/' + product.image1) }} class="image"></a>
<p class="name">{{ product.name }}</p>
<p class="price">{{ product.price }}</p>
</div>
Also, I suggest investigating the MVC design pattern which is highly prevalent in Flask applications.

Related

Display counter of likes per post with Django is not working

I want to get a variable on the views.py file that retrieves the list of likes for each post. So, then on the HTML file, I would use .count so I can get the number of items on the list and finally be displayed on the DOM.
I first made classes on models.py. There, I have 3 classes: User, Post, and Like. User is from the default User class from Django. Post is a class that gets information about the post like the author, description, and timestamp. And on the Like class, I get the user and post.
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
pass
class Post(models.Model):
author = models.ForeignKey("User", on_delete=models.CASCADE, related_name="user")
description = models.CharField(max_length=1000)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.id}: {self.author}"
class Like (models.Model):
user = models.ForeignKey("User", on_delete=models.CASCADE, default='', related_name="user_like")
post = models.ForeignKey("Post", on_delete=models.CASCADE, default='', related_name="post_like")
def __str__(self):
return f"{self.id}:{self.user} likes {self.post}"
Second, I made a function on views.py called "index". There, I get the whole list of posts (on the posts variable), then I tried to create the variable (totalLikesSinglePost), which should get the list of likes for each post.
def index(request):
posts = Post.objects.all().order_by("id").reverse()
# Pagination Feature (OMIT THIS, IF YOU WANT)
p = Paginator(posts, 10)
pageNumber = request.GET.get('page')
postsPage = p.get_page(pageNumber)
# Total likes of each post. DOESN'T WORK ⏬
for postSingle in posts:
totalLikesSinglePost = Like.objects.all().filter(post = postSingle)
return render(request, "network/index.html", {
"posts": posts,
"postsPage": postsPage,
"totalLikesPost": totalLikesSinglePost
})
Finally, on the HTML file, there I get each post with its information and the number of likes. However, the output just displays the number 0
{% for post in postsPage %}
<div class="row">
<div class="col">
<div class="card h-100" id="post-grid">
<div class="card-body">
<h5>{{ post.author }}</h5>
<div> | {{ post.timestamp }}</div>
<p>{{ post.description }}</p>
<div class="card-buttonsContainer">
<strong style="color: red">{{ totalLikesPost.count }}</strong> <!--0 IS DISPLAYED-->
</div>
</div>
</div>
</div>
</div>
{% endfor %}
And of course, after that, I created a superuser and went to admin to add some examples to the database. However, I added some new rows on the Like table but does not display the total of likes for each post.
Your totalLikesSinglePost is just a list of Like records of the last post.
Try this on your html file:
<strong style="color: red">{{ post.post_like.count }}</strong>
Because totalLikesSinglePost get override in each round of for loop. It is the total number of likes of the last post.
You need to assign this number to the post as an attribute, like
for post in posts:
post.num_likes = Like.objects.all().filter(post=post).count()
And then in your template
{% for post in posts %}
{# ... #}
{{ post.num_likes }}
{% endfor %}
However, I strongly recommend you using .annotate
from django.db.models import Count
posts = Post.objects.all().order_by('-id')\
.annotate(num_likes=Count('like'))
This will access database only once and save you a lot of time.

How to dynamically generate URL with flask and my database?

I try to dynamically generate my url_for like this and it is not working...:
search.html
<a href="{{ url_for('{{ country }}') }}"
This is where I query my data for my link from my database.
routes.py
from app import app, db
from app.models import
#app.route('/search')
def search():
country = Country.query.get(3)
return render_template('search.html', country=country.country)
#this is one of the final page where the link in the search results lead
#I will have /portugal, /france, etc...
#app.route('/germany')
def germany():
return render_template('germany.html')
and this is a clip of the information in my database:
models.py
from app import db
class Country(db.Model):
id = db.Column(db.Integer, primary_key=True)
country = db.Column(db.String(120), index=True, unique=True)
html_page = db.Column(db.String(255), index=True, unique=True)
def __repr__(self):
return '<Country {}>'.format(self.country)
Do I even need to store the URL since it is the same as the country name aka #germany
If you already have a base template file setup for your countries then typically you want to generate that template with the specific information.
Then typically you want to have your function definition setup something like this
# renders the given country page from the base template
# EX: if given "Germany" the resulting url will be:
# mywebsite.com/country/Germany
#app.route("country/<str:country>", methods=["GET"])
def render_country(country):
# your specific code here
return render_template("countrybase.html", country=country)
While your html link would look something like this:
Link to Germany page
If you don't already have a base html template setup I would recommend researching Jinja templates and how Flask uses them. The example project from the official documentation has a great guide on how to get started.
But as a quick example your countrybase.html might look like this:
{% extends base.html %}
{% block header %}
<h1>{% block title %}{{ country["name"] }}{% endblock %}</h1>
{% endblock %}
{% block content %}
<div class="my-custom-css-class">
{{ country["other_country_data"] }}
<!-- etc.. -->
</div>
{% endblock %}

How to display results from a database in a django web page?

I have a web page built in django that shows all rows of a sqlite table in bootstrap cards. Basically each card is a row of the table.
I want to give the user the ability to filter these cards, so the page instead of showing every objects in the database's table should display only the objects relevant to the user. The result of the search can be displayed at another URL, it's irrelevant.
I can build a html form to get info from the user and I can write a python function that search the database and print out specific rows based on an input keyword,
but I can't make these things work together.
Any hint or piece of code would be a great help, unfortunately I know nothing about php so if there's a way to do so without it would be great. Thanks all.
# this is my view function, it shows all the objects in the table at a #specific URL
def index_u(request):
universities = University.objects.all()
return render(request, 'index_uni.html', {'universities': universities})
/* this is index_uni.html */
{% block content %}
<div class="row">
{% for university in universities %}
<div class="col">
<div class="card" style="width: 18rem;">
<img src="{{ university.image_url}}" class="card-img-top">
<div class="card-body">
<h5 class="card-title">{{university.name}}</h5>
<p class="card-text">{{university.region}}</p>
Go
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
#this function takes an input and diplays specific rows
#I don't know how make this works in the html page, it works fine in terminal
def search_data():
region_choice = input("insert region here: ")
command = "SELECT name FROM university_university WHERE region = ?;"
c.execute(command, [region_choice])
for row in c:
print(row)
#here my model
class University(models.Model):
name = models.CharField(max_length=58, default=True)
city = models.CharField(max_length=58, default=True)
region = models.CharField(max_length=58, default=True)
country = models.CharField(max_length=58, default=True)
number_of_students = models.IntegerField(default=True)
image_url = models.CharField(max_length=2083, default=True)
is_public = models.BooleanField(default=True)
ranking = models.IntegerField(default=True)
colleges = models.BooleanField(default=True)
scholarship = models.BooleanField(default=True)
uni_canteen = models.BooleanField(default=True)
page_url = models.CharField(max_length=2083, default=True)
You can make a query with:
def index_u(request):
region = request.GET.get('region')
universities = University.objects.all()
if region is not None:
universities = universities.filter(region=region)
return render(request, 'index_uni.html', {'universities': universities})
You can then create for example a form, like:
<form method="get" action="url-of-index_u">
<input type="text" name="region">
<input type="submit">
</form>
<! -- render the universities -->

Flask SQLAlchemy Query

I have created a Flask project using Miguel Grinbergs' excellent tutorials. I am adapting the example code to make my own site.
I have two tables in my db. Drivers and Teams. I have created a One-to-Many relationship in models.py and I want to output a list of drivers and the team they belong to. I have included routes.py and the html file that will display the results.
The query in routes.py works fine if I specify one driver within the query, but when I try Drivers.query.all(), it does not return any data on the web page.
NOTE = I have ran the Drivers.query.all() within a Python Shell and I get the correct response, it is only when I try to implement the solution on the web page that I experience the problem.
output from Python Shell
>>> details = Driver.query.all()
>>> details
[<Driver Lewis Hamilton>, <Driver Kimi Raikkonen>, <Driver Max Verstappen>]
>>> for d in details:
... print(d.driverName, d.driverTeam)
...
Lewis Hamilton Mercedes
Kimi Raikkonen Ferrari
Max Verstappen Red Bull
>>>
models.py
class Team(db.Model):
id = db.Column(db.Integer, primary_key=True)
teamName = db.Column(db.String(64))
teamDriver = db.relationship('Driver', backref='driverTeam', lazy='dynamic')
def __repr__(self):
return '{}'.format(self.teamName)
class Driver(db.Model):
id = db.Column(db.Integer, primary_key=True)
driverName = db.Column(db.String(64))
team_id = db.Column(db.Integer, db.ForeignKey('team.id'))
def __repr__(self):
return '<Driver {}>'.format(self.driverName)
routes.py
#app.route('/driverDetails', methods=['GET', 'POST'])
def driverDetails():
## Output works fine with one driver specified in query
# details = Driver.query.filter_by(driverName='Kimi Raikkonen').first()
## No data is output when using query below
details = Driver.query.all()
return render_template('driverDetails.html', title='Driver Details', details=details)
driverDetails.html
{% extends "base.html" %}
{% block content %}
<h1>Driver Details</h1>
<!-- Output works correctly for one driver as specified in query on routes.py
<div><p>{{ details.driverName }} is in: <b>{{ details.driverTeam }}</b></p></div>
-->
<!-- However, when trying to iterate over whole list of drivers, output shows
the text "drives for:". But there is no data being populated from the db
-->
{% for d in details %}
<div><p>{{ details.driverName }} drives for: {{ details.driverTeam }}</p></div>
{% endfor %}
{% endblock %}
I would appreciate any advice on where I am going wrong.
your code is basically correct, you just need to actually use the object you are binding to d:
{% for d in details %}
<div><p>{{ d.driverName }} drives for: {{ d.driverTeam }}</p></div>
{% endfor %}

Creating a blog with Flask

I'm learning flask and I have a little problem.
I made an index template, where are blog post titles.
{% for title in titles %}
<!-- Main Content -->
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div class="post-preview">
<a href="{{ url_for('post')}}">
<h2 class="post-title">
{{ title[0] }}
</h2>
</a>
<p class="post-meta">Posted by {{ author }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
Here is part of my post.html template.
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<p>{{ post_text1 | safe }}</p>
<hr>
<div class="alert alert-info" role="alert">Posted by
{{ author }}
</div>
</div>
</div>
</div>
I'm using sqlite3. Currently every title leads to same post.html where is first text from first post.
How to make every title direct to their post text? I mean, if I click on the first title it should bring up post.html and there should be first text. Second title should show second text.
Should I write program, that creates new html for every post or is there any other way?
#app.route('/')
def index():
db = connect_db()
titles = db.execute('select title from entries')
titles = titles.fetchall()
author = db.execute('select author from entries order by id desc')
author = author.fetchone()
return render_template('index.html', titles=titles[:], author=author[0])
#app.route('/post/')
def post():
db = connect_db()
post_text1 = db.execute('select post_text from entries')
post_text1 = post_text1.fetchone()
author = db.execute('select author from entries where id=2')
author = author.fetchone()
return render_template('post.html', post_text1=post_text1[0], author=author[0])
The problem comes from here <a href="{{ url_for('post')}}">.
What this tells Flask is to make a url for post, which is something you have defined in views as def post(argument) but you are not providing an argument. So if for example you are making you are taking your posts based on id, your view would would ask for /<int:post_id>/ in url and post_id would be passed as an argument based on which you would find the specific post and pass that to the template.
Your url_for should reflect this, you should have {{ url_for('post', post_id=title[1]) }} or wherever you are storing your equivalent of post_id (maybe that's title for you).
Edit:
Baed on your edit, your problem is that you are not telling Flask which post to fetch. You need either ID, or slug, or something that will go in the url and will tell you which post you are looking for. Your function right now is static and is always fetching the first entry in your database. The changes required are:
#app.route('/')
def index():
db = connect_db()
titles = db.execute('select title, id from entries')
titles = titles.fetchall()
author = db.execute('select author from entries order by id desc')
author = author.fetchone()
return render_template('index.html', titles=titles, author=author[0])
#app.route('/post/<int:post_id>/')
def post(post_id):
db = connect_db()
post_text = db.execute('select post_text from entries where id = ?', post_id)
post_text = post_text1.fetchone()
author = db.execute('select author from entries where id=2')
author = author.fetchone()
return render_template('post.html', post_text1=post_text, author=author)
<a href="{{ url_for('post', post_id=title[1])}}">
Also your author fetching is weird, you should have them stored (at least their ids) next to entries. Then I'd recomend some naming changes etc. It's hard to just answer the question and not write the code for you, as this is a site for answering specific questions, not writing code on demand :) Try to understand what I wrote here, play around with it a bit more etc. to fully undnerstand.
tl;dr: Posts have to get an argument and then fetch a post identified by that argument, the program can't magically tell which post you clicked on.

Categories