I have two models within a flask application, they form a one-to-one relationship, where the 'Staff' model can be associated with one 'User'. There can be 'Staff' without them being a 'User', but not a 'User' without being 'Staff'.
Unfortunately, I'm falling at the first hurdle. I'm trying to combine the models in a HTML table, templated through Jinja2. The table is supposed to list all 'Staff' and identify whether they are a 'User'. If they are a 'User' it is supposed to identify whether or not they are active.
Here are my models as they stand:
class Staff(db.Model):
__tablename__ = 'staff'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
job_title = db.Column(db.String(255), nullable=True)
is_user = db.Column(db.Boolean, nullable=False, default=False)
user = db.relationship('User',
backref=db.backref('staff', lazy=True))
def __repr__(self):
return f'Staff Member: {self.id}, {self.name}, {self.job_title}'
class User(db.Model, UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False,
default='default.jpg')
password = db.Column(db.String(255), nullable=False)
active = db.Column(db.Boolean, nullable=False, default=False)
staff_id = db.Column(db.Integer, db.ForeignKey('staff.id'),
nullable=False)
def __repr__(self):
return f'User({self.name}, {self.email}, {self.image_file})'
And here is an example of the relationship in Jinja2. This code segment works as expected:
{% for user in users %}
<tr>
<td>{{ user.staff.name }}</td>
<td>{{ user.staff.job_title }}</td>
<td>{{ user.email }}</td>
{% if user.active == True %}
<td>Yes</td>
{% else %}
<td>No</td>
{% endif %}
{% endfor %}
This code, however, where I'm trying to get 'User' details while looping through all 'Staff' doesn't work at all:
{% for staff in all_staff %}
<tr>
<td>{{ staff.name }}</td>
<td>{{ staff.job_title }}</td>
{% if staff.is_user == True %}
{% if staff.user.active == True %}
<td class="text-success">Yes (Active)</td>
{% elif staff.user.active == False %}
<td class="text-warning">Yes (Inactive)</td>
{% endif %}
{% elif staff.is_user == False %}
<td class="text-danger">No</td>
{% endif %}
{% endfor %}
While the relationship works one way it doesn't work in the second table example; it returns no HTML code at all unless the staff.is_user == False statement is true.
I've tried to create a new db.relationship in the 'User' class. I've also tried a db.relationship in both classes at once. I've tried the Jinja2 if statements on the same line, as well:
{% if staff.is_user == True and staff.user.active == True %}
None of these are working however, and where I'd expect the correct html to be returned based on the results of the if statements, I'm getting no results at all when referring to the 'Users' table, via the 'Staff table.
I'm sure I'm missing something simple but I can't figure it out.
Can anybody see where I'm going wrong?
It turns out the answer was hiding in the 'One-to-May Relationships' section in the Flask-SQLAlchemy docs. In order to reference a one-to-one relationship, you need to set uselist=False in the relationship. Exactly as it sounds, it loads the connection as a scalar rather than a list.
I'd also made an error in taking some information from the main SQLAlchemy docs and not correctly loading the backref in the relationship. In Flask-SQLAlchemy, it simply needs to be declared as a string. In this case backref='staff'.
This code works exactly as expected:
class Staff(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True, nullable=False)
job_title = db.Column(db.String(80))
is_user = db.Column(db.Boolean, default=False)
user = db.relationship('User', backref='staff', lazy=True, uselist=False)
def __repr__(self):
return f'Staff Member: {self.id}, {self.name}, {self.job_title}'
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
active = db.Column(db.Boolean, nullable=False, default=False)
staff_id = db.Column(db.Integer, db.ForeignKey('staff.id'), nullable=False)
def __repr__(self):
return f'User Account: {self.id}, {self.email}'
Related
In the following snippet I put a template similar to my implementation. Where we have a "Grandparent" object with a OneToMany relationship with the "Parent" object and this the same relationship with the "Child" object.
class Grandparent(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True, nullable=False)
name = db.Column(db.String(50), nullable=False, unique=True)
parents = db.relationship('Parent', backref='grandparent')
class Parent(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True, nullable=False)
name = db.Column(db.String(50), nullable=False, unique=True)
grandparent_id = Column(Integer, ForeignKey('grandparent.id'))
children = db.relationship('Child', backref='parent')
class Child(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True, nullable=False)
name = db.Column(db.String(50), nullable=False, unique=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
What I want to do is get a list through the child object with information from the father and grandfather. The jinja code is as follows:
<table>
<thead>
<tr>
<th>Children name</th>
<th>Parent name</th>
<th>Grandparent name</th>
</tr>
</thead>
<tbody>
{% for child in children %}
<tr>
<td>{{ child.name }}</td>
<td>{{ child.parent.name }}</td>
<td>{{ child.parent.grandparent.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
I get the following error:
jinja2.exceptions.UndefinedError: 'None' has no attribute 'grandparent'
It fails when performing the 2 backref, I don't know how it should be done correctly, since in the official guide it is implemented in this way.
def add(model, **kwargs):
instance = model(**kwargs)
db.session.add(instance)
db.session.commit()
def getAll(model):
data = model.query.all()
return data
I use the function above to get all the objects from the database or add a new one.
So when the endpoint is reached I do the following:
#bp.route('/example/')
def example():
children = getAll(Child)
return render_template('index.html', children=children)
Solution: all objects must have associated parent.
Hi I'm newbie to web development. I'm using flask and I have this ff. error:
"Could not build url for endpoint 'post'. Did you forget to specify values ['post_id']?"
I'm trying to redirect the page after deleting a comment in the post. But it brings me to "500 Internal Server Error" page and when I click the back button of my browser, the success flash msg is displayed and the comment was remove.
"routes.py"
#app.route("/post/<int:post_id>")
#login_required
def post(post_id):
post = Post.query.get_or_404(post_id)
return render_template("post.html", title="Post", post=post)
#app.route("/post-comment/<int:post_id>", methods=['GET','POST'])
#login_required
def comment_post(post_id):
post = Post.query.get_or_404(post_id)
form = CommentForm()
if form.validate_on_submit():
comment = Comment(body=form.body.data, post_id=post.id, author=current_user)
db.session.add(comment)
db.session.commit()
flash("Your comment has been added to the post", "success")
return redirect(url_for("comment_post", post_id=post.id, post=post))
return render_template("comment.html", title="Comment Post", post_id=post.id, post=post, form=form)
#app.route("/comment-delete/<int:comment_id>", methods=['POST'])
#login_required
def delete_comment(comment_id):
comment = Comment.query.filter_by(id=comment_id).first()
if comment.author != current_user:
abort(403)
db.session.delete(comment)
db.session.commit()
flash('Your comment has been deleted!', 'success')
return redirect(url_for('post'))
"post.html and comment.html" - both has url_for to delete_comment
{% for comment in post.comments %}
<table>
<tr>
<td>
{% if comment.author == current_user %}
<form action="{{url_for('delete_comment', comment_id=comment.id, post_id=post.id, post=post)}}" method="POST">
<input class="btn btn-danger" type="submit" value="Delete">
</form>
{% endif %}
<small class="text-muted">{{ comment.author.username }}</small>
<p class="mb-1">{{ comment.body }}</p>
</td>
</tr>
</table>
{% endfor %}
DB
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
comments = db.relationship('Comment', backref='author', lazy=True)
posts = db.relationship('Post', backref='author', lazy=True)
def __repr__(self):
return f"User('{self.username}')"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
comments = db.relationship("Comment", backref="post", cascade="all, delete-orphan", lazy=True)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(100), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
def __repr__(self):
return f"Comment('{self.body}')"
In the delete_comment function, when you are redirecting to the post function, you have to pass the post_id also.
It should be something like this:
return redirect(url_for('post', post_id=post.id))
For this to work you have to fetch the post id from the database.
I've been trying to build a web to check student attendance. I'm aiming to get a list of students of a classroom on a webpage
User class:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(80), unique=True, nullable=False)
name = db.Column(db.String(20), nullable=False)
password = db.Column(db.String(60), nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
occupation = db.Column(db.String(20), nullable=False)
year = db.Column(db.String(20), nullable=False)
classroom = db.Column(db.Integer, db.ForeignKey('classroom.id'))
posts = db.relationship('Post', backref='author', lazy=True)
def __repr__(self):
return f"User('{self.email}', '{self.name}', '{self.image_file}')"
Classroom class:
class Classroom(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True, nullable=False)
description = db.Column(db.String(80))
users = db.relationship('User', backref='user', lazy=True)
#classmethod
def get_classroom(cls):
classroom_list = Classroom.query.all()
return [(classroom.id, classroom.name) for classroom in classroom_list]
def __repr__(self):
return f"Classroom('{self.name}', '{self.description}')"
However, as classrooms were not created at first, no foreign keys were actually given to students in regard to their classrooms, then set as Null by default.
So, I was going to update the foreign keys by picking out one selection from a drop-down with HTML only but I could not figure out a proper solution and decided to try it with another method. A new form for that is now created and then it seems alright on a webpage. but the code isn't actually working well internally and posing some problems.
Form:
class SelectClassroomForm(FlaskForm):
name = StringField('Name')
year = StringField('Year')
classroom = SelectField('Classroom',
choices=Classroom.get_classroom(),
)
submit = SubmitField('Add')
Trying to update foreign keys:
#app.route("/students", methods=['GET', 'POST'])
#login_required
def students():
form = SelectClassroomForm()
students = User.query.filter(User.year != "I'm not a Student")
if form.validate_on_submit():
students.classroom = form.classroom.data[0]
db.session.commit()
flash('Successfully Added classroom info to students', 'success')
return redirect(url_for('students'))
else:
flash("something's wrong", 'danger')
return render_template('students.html', students=students, form=form)
I'm not sure whether it is the code above or HTML code down below that is stopping me from updating the keys..
HTML Code:
{% extends "layout.html" %}
{% block content %}
<h2>Student List</h2>
<div class="content-section">
<div class="table-responsive">
<table id="mytable" class="table table-bordered table-striped">
<thead>
<th> Name </th>
<th> Year </th>
<th> Classroom </th>
</thead>
<tbody>
{% for student in students %}
<tr>
<td> {{ student.name }} </td>
<td> {{ student.year }} </td>
<td>
<form method="POST" action="">
{{ form.classroom(class="form-control form-control-sm") }}
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<a class="btn btn-primary btn-sm mt-1 mb-1" href="{{ url_for('students') }}">Update</a>
</div>
{% endblock content %}
(sorry about the uncolored codes by the way I was trying to find out how to but failed on my own page)
Anyways, Here the webpage shows the list of students as it was supposed to be but when I click on the update button, that's when the problem turns up. How can I solve it and then successfully update students' info with new foreign keys? Any help will be much appreciated Thank you.
I am trying to setup a OneToOne relationship in Django in order to show data from TableA and TableB for a specific record.
I am having issues figuring out how to display it in my view, examples are out there but some seem a bit outdated or have solutions that I am not familiar with.
I have tried with some different calls in the views file as well as in my templates file without any luck, any input would be highly appreciated!
My models.py
from django.db import models
# Create your models here.
class tableA(models.Model):
created = models.DateTimeField(default=None)
published = models.CharField(max_length=50, default=None)
publisher = models.CharField(max_length=50, default=None)
code = models.CharField(max_length=25, null=True, unique=True)
class Meta:
db_table = 'tableA'
unique_together = (("publisher", "published"),)
def __str__(self):
return self.created
class tableB(models.Model):
tableA = models.OneToOneField(tableA, on_delete=models.CASCADE, primary_key=True, default=None)
code = models.CharField(max_length=50, default=None, null=True)
url = models.CharField(max_length=100, default=None, null=True)
class Meta:
managed = True
def __str__(self):
return self.tableA.code
My views.py
def nani(request):
data = TableA.objects.all()
return render(request, 'site/home.html', {'data':data})
My template
{% for test in data %}
<tr>
<td>{{ test.published }}</td>
<td>{{ test.publisher }}</td>
<td>{{ test.TableB.url }}</td>
</tr>
{% endfor %}
If the uppercase/lowercase letters are like you said, just do this, I just tested it.
views.py
from .models import tableA
def nani(request):
data = tableA.objects.all()
return render(request, 'site/home.html', {'data': data})
in your site/home.html :
{% for test in data %}
{{ test.published }}
{{ test.publisher }}
{{ test.tableb.url }}
{% endfor %}
and also something wrong about your str method. datetime is not a string format. so in your models.py, you have to fix this line like this:
def __str__(self):
return str(self.created)
I have basic models for a section, subsection and clause. 1 section can hold multiple subsections. Each subsection can hold multiple clauses. The models look like:
**models.py**
class Clause(models.Model):
number = models.CharField(max_length=8, unique=True)
requirements = models.TextField(max_length=2000, unique=False, blank=True, null=True)
documentation = models.TextField(max_length=2000, unique=False, blank=True, null=True)
class Subsection(models.Model):
number = models.CharField(max_length=5, unique=True)
name = models.CharField(max_length=150, unique=False)
descriptor = models.TextField(max_length=2000, unique=False, blank=True, null=True)
clause = models.ForeignKey(Clause, on_delete=models.DO_NOTHING, related_name="clause")
class Section(models.Model):
number = models.CharField(max_length=2, unique=True)
name = models.CharField(max_length=150, unique=False)
descriptor = models.TextField(max_length=2000, unique=False, blank=True, null=True)
subsection = models.ForeignKey(Subsection, on_delete=models.DO_NOTHING, related_name="subsection")
basic view function to call the desired section:
**views.py**
def main(request):
form = MainSearchForm()
user = request.user
sections = []
show_results = True
if 'query' in request.GET:
show_results = True
query = request.GET['query'].strip()
if len(query) <= 2:
sections = Section.objects.filter(number__iexact=query)
if sections:
records = sections
tpl = "display_console.html"
context = {'user': user, 'records': records, 'form': form}
return render(request, tpl, context)
else:
tpl = "main.html"
context = {'user': user, 'form': form}
return render(request, tpl, context)
unfortunately, I can't get my template to return my subsection data. The following returns a 'Subsection' object is not iterable error:
**template**
<table class="section_tb">
{% if records %}
{% for record in records %}
<tr>
<td>{{ record.number }}</td><td>{{ record.name }}</td>
</tr>
{% for item in record.subsection %}
<tr>
<td>{{ item.number }}</td><td>{{ item.name }}</td>
</tr>
<tr>
<td colspan=2>{{ item.descriptor }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
{% else %}
{% endif %}
substituting:
{% for item in record.subsection.all %}
for:
{% for item in record.subsection %}
removes the error message, but doesn't return any data. Is there something obvious I'm doing wrong?
This is because Section can have only one Subsection.
So you can access the subsection with just {{record.subsection}} so no forloop needed here.
As a tip remember when you usea one to many, the one is where the foreign key is defined.
The model that store the foreign key will always store only one foreign key.
If you want to access the many foreign keys from the other side use the model_name_in_lowercase_set or define a related name in models.ForeignKey(..., related_name="something") then you can call something_set