How to update foreign keys with new values properly? - flask

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.

Related

Django, Unable to view certain database information

I've been working on a project and I've been stuck for over a week and haven't been able to find the solution to this problem.
I'm creating a school management app. I've successfully allowed the teachers to create a schedule, allowed the students to view it and select it as part of their class. The problem I'm running into is that the teacher is not able to see the students information after they've chosen a class.
For models I have:
class Student(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
englishName = models.CharField(max_length=200)
studentName = models.CharField(max_length=200)
studentId = models.CharField(max_length=200)
birthday = models.DateField()
gender = models.CharField(max_length=6)
gradeLevel = models.CharField(max_length=8)
since = models.DateField()
duration = models.CharField(max_length=3)
contactNumber = models.CharField(max_length=13, default=00000000)
email = models.EmailField(max_length=200, default='address#email.com')
def __str__(self):
return self.studentName
def index(request):
data=Student
context = {'form': data}
return render(request, 'student-information.html', context)
class Schedule(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
date = models.DateField(null = True, blank = True)
time = models.TimeField(null = True, blank = True)
duration = models.TextField(null = True, blank = True)
capacity = models.PositiveSmallIntegerField(default=1)
student = models.ForeignKey(Student, null = True, on_delete=models.CASCADE)
def __datetime__(self):
return self.date + '' + self.student
def index(request):
data = Schedule
context = {'form': data}
return render(request, 'set-schedule.html', context)
For my views file I have:
#login_required
#allowed_users(allowed_roles=['teacher'])
def dailySchedule(request):
today = datetime.now().date()
schedules = Schedule.objects.filter(date=today)
students = []
for schedule in schedules:
students.append(schedule.student)
context = {'today': datetime.now().date(), 'schedules': schedules, 'students': students}
return render(request, 'eslbeeph/daily-schedule.html', context)
and for the html file I have:
{% extends 'main.html' %}
{% block content %}
<div class="container-fluid">
<div class="daily-schedule-title">
<h3 class="daily-schedule-header">Schedule</h3>
</div>
<!--Information to be obtained by from the database.-->
<div class="daily-schedule-table">
<table class="daily-schedule">
<tr>
<th>Date</th>
<th colspan="6">{{ today|date:"F d, Y" }}</th>
</tr>
<tr>
<th>Start Time</th>
<th>Account</th>
<th>Duration</th>
<th>Student ID</th>
<th>Student Name</th>
<th>Class Status</th>
<th>Student Profile</th>
</tr>
{% for schedule in schedules %}
<tr>
<td>{{ schedule.time|date:"H:i" }}</td>
{% for student in students %}
<td>{{ schedule.student.user.username }}</td>
<td>{{ schedule.duration }}</td>
<td>{{ schedule.student.studentId }}</td>
<td>{{ schedule.student.englishName }}</td>
{% endfor %}
<td>{ status }</td>
<td></td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock content %}
Now, There's a lot more code to the project but these are the areas I think are causing the problem. I've tried a number of different things to get this to work, but nothing's worked.
If anyone can see where I'm going wrong or what I need to do let me know. I appreciate any help anyone can give me.
What I've been trying to do is get the a table to show up with the schedule time, the user name of the student who selected the schedule, the number of minutes the teacher stated the class would be, the students id number, and the students english name. The class status and the button for the students profile I plan to handle later.
Right now the time and the class length show up fine. But no matter what I do I cannot get the students user name, english name, and ID to populate at all.
Any tips, examples, or guesses as to why this is happening would be appreciated.
EDIT
After doing a bit more work I found that the Student model is not connecting to the Schedule model. So, the problem is different from what I originally suspected. It's not that the student information isn't being shown. It's that the schedule a student selects isn't recognizing the student has selected it.

How to check if the object is assigned to another object | Django

I am working on a django case like below:
models.py
class Room(models.Model):
name = models.CharField("Room No.",max_length=200)
class Meta:
verbose_name_plural = "Room"
def __str__(self):
return self.name
class Student(models.Model):
name = models.CharField("name",max_length=200)
father_name = models.CharField("father Name",max_length=200)
cell_no = models.CharField("cell No",max_length=200)
address = models.CharField("address",max_length=500)
room = models.ForeignKey(Room, on_delete=models.CASCADE, null=True, blank=True, related_name='all_rooms')
class Meta:
verbose_name_plural = "Student"
def __str__(self):
return self.name
views.py
def room(request):
allrooms= Room.objects.all()
form = RoomForm(request.POST or None, request.FILES or None)
if form.is_valid():
form.save()
messages.success(request, "Room added successfully.")
return redirect('/room')
context = {'allrooms':allrooms, 'form':form}
return render(request, 'room.html', context)
In templates in room.html I want to show the status Vacant/Occupied based on the fact if a room is assigned to some student or not. I have the following code in template but it shows 'Vacant' status for all rooms.
<table id="example1" class="table table-bordered table-striped">
<thead>
<tr>
<th>Room</th>
<th class="text-center">Status</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
{% for room in allrooms %}
<tr>
<td>{{ room.name }}</td>
<td class="text-center">
{% if room.student_set.all %}
<small class="badge badge-danger">Occupied</small>
{% elif not room.student.all %}
<small class="badge badge-success">Vacant</small>
{% endif %}
</td>
<td class="text-center"><i class="fas fa-edit"></i></td>
</tr>
{% endfor %}
</tbody>
</table>
Please help someone to show he status of the room.
to get assigned and unassigned rooms you have to write queries with respect to the related field(in this case the foreign key "all_rooms") as follows:
total_rooms = Room.objects.all().annotate(num_rooms=Count("all_rooms"))
assigned_rooms = total_rooms.filter(num_rooms__gt=0)
unassigned_rooms = total_rooms.exclude(num_rooms__gt=0)
On running, these queries will return the room instances:

Django : Dynamically update front end dropdown based on the end user's data in a product model

I would like to have dropdowns filters for users browsing their book collection. The dropdown values are currently populated with every corresponding field value in the model, I only want users to get values relevant to them e.g. I have only publisher associatted to my books, 'Marvel', so I should only see Marvel in the publisher drop down when I go to filter my books.
I am not able to pass the user value to the form drop downs, even after setting up the initialization function. I keep getting error no such attribute as 'uid' or'user' in the view when I am passing the value to the form.
Models.py
class ComicInput(models.Model):
Publisher = models.CharField(max_length=20, default='Marvel', choices=Publisher_Choices, null=True, blank=True )
Title = models.CharField(max_length=50,default='', blank=False)
Type = models.CharField(max_length=30, choices=Type_Choices, null=True, blank=True ) #default='Reg'
Number = models.IntegerField(default='', blank=False)
Category = models.CharField( max_length=12,default="Hold",choices=Category_Choices,blank=True, null=True)
uid = models.ForeignKey(User,on_delete=models.CASCADE, editable=False) #default=False, null=True)
def __unicode__(self):
return '%s %s %s' % (self.uid,self.Title, self.Number, self.Grade, self.Series, self.CoverPic, self.Category)
class Meta:
ordering = ('Title', 'Series', 'Number')
Views.py
###################### Collection Viewer #############
#login_required(login_url="/login/")
def ComicInventory(self):
title_list = TitleChoiceField()
publisher_list = PublisherChoiceField()
sellingnotes_list = NotesChoiceField()
category_list = CategoryChoiceField()
if self.GET.get('titles'): # On screen drop down Filter for titles
selected_title = self.GET.get('titles')
displayInventory=ComicInput.objects.filter(Title=selected_title,uid=self.user)
DisplaySumValue=ComicInput.objects.all().filter(Title=selected_title,uid=self.user).aggregate(Sum('Value'))
else:
displayInventory=ComicInput.objects.filter(uid=self.user)
DisplaySumValue=ComicInput.objects.all().aggregate(Sum('Value'))
context = {
'displayInventory': displayInventory,
'DisplaySumValue': DisplaySumValue,
'title_list': title_list,
}
return render(self, 'app/viewer.html',context)
HTML
<body>
<h1><Strong>Here are your comics;</Strong></h1>
<div class="panel-heading">
**<!.. this is the Choice Field on HTML ..!>**
<div class="panel-title pull-left">
<form method="get" action="{% url 'ComicInventory' %}">
{{ category_list }}
<input type="submit" value="Filter">
</form>
</div>
<div class="container">
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th scope="col">Publisher</th>
<th scope="col">Title</th>
<th scope="col">Number</th>
<th scope="col">Edition</th>
</tr>
</thead>
{% for inv in displayInventory %}
<tbody class="table table-hover">
<tr>
<td>{{inv.Publisher}}</td>
<td>{{inv.Title}}</td>
<td>{{inv.Number}}</td>
<td>{{inv.Edition}}</td>
alt="{{inv.Publisher}} image",height="60", width="100" /></a></td>
<td> Edit </td>
<td> Delete </td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td><b>Total Value: {{DisplaySumValue}} </b></td>
</tr>
</tfoot>
</table>
</div>
</body>
EDIT
Form.py
##Updated Model ChoiceField that initiates self, so I can get the user and pass it to the view ##
class TitleChoiceField(forms.Form):
class Meta:
model = ComicInput
fields = ('Title', 'uid',)
def __init__(self,uid, *args, **kwargs):
super(TitleChoiceField, self).__init__(*args, **kwargs)
self.fields['titles'].queryset=ComicInput.objects.filter(uid=self.user).values_list("Title", flat=True).distinct().order_by('Title')
Django AttributeError: Form object has no attribute '_errors'
Updated the forms like so based on the above post:
Forms.py
class TitleChoiceField(forms.Form):
class Meta:
model = ComicInput
fields = ('Title','uid',)
titles = forms.ModelChoiceField(queryset =ComicInput.objects.all())
def __init__(self, uid=None, *args, **kwargs):
super(TitleChoiceField, self).__init__(*args, **kwargs)
self.user = uid
usrqry = ComicInput.objects.filter(uid=self.user).values_list('Title', flat=True).distinct().order_by('Title')
self.fields['titles'].queryset=usrqry

django: values() dictionnary with many to many relationship

I'm trying (for training purposes) to make an application for a library catalogue. I took the decision to make a Person model for all people involved in creation of the book and join it with Book model through intermediary model (since one book may have several authors, redactors, translators etc), which would describe the role of this person.
The Book model is for an edition of a book. It stores information about book as a piece of literature. For each edition of a book there is a book instance.
The copy model is for a book as an object.
Now I'm trying to make search function. My goal is to display a list of all book which meets search criteria, but have all copies which share the authors and the title (regardless the fact, if it was the same publisher, translator, etc), as one row with the number indicating the number of copies. So, I understand, I should use annotate with Count on values() dictionnary. But I'm not able to get all information I need while using values().
If I make a query searching by the book title, I have all creators in the values() dictionnary. But if the search criteria is author, the values dictionnary keeps only the name which was used to filter, so if the book has two or three authors, I don't get their names.
Here are my models (for Book model only the relevant fields, to make shorter):
class Person(models.Model):
name = models.CharField(max_length=128, blank=True)
born = models.CharField(max_length=64, blank=True)
class Book(models.Model):
title = models.CharField(max_length=255, blank=True)
creators = models.ManyToManyField(Person, through='Creator')
class Copy(models.Model):
on_shelf = models.BooleanField()
location = models.CharField(max_length=32, blank=True)
remarques = models.CharField(max_length=255, blank=True)
signature_mark = models.CharField(max_length=32, blank=True)
approuved = models.BooleanField(default=False)
collection = models.ForeignKey(Collection, on_delete=models.CASCADE, blank=True, null=True)
book = models.ForeignKey(Book, on_delete=models.CASCADE, null=True)
class Creator(models.Model):
AUTHOR = 'A'
TRANSLATOR = 'T'
REDACTION = 'R'
INTRODUCTION = 'I'
ROLE_CHOICES = [
(AUTHOR, 'Autor'),
(TRANSLATOR, 'Tłumacz_ka'),
(REDACTION, 'Opracowanie, redakcja [Nazwisko Imię]'),
(INTRODUCTION, 'Wstęp, posłowie'),
]
person = models.ForeignKey(Person, on_delete=models.CASCADE)
book = models.ForeignKey(Book, on_delete=models.CASCADE, blank=True, null=True)
role = models.CharField(max_length=32, blank=True, choices=ROLE_CHOICES)
Here are my queries:
By title:
book = Copy.objects.filter(book__title='Rozwój cywilizacji amerykańskiej tom 1-2').values('book__creators__name', 'book__creator__role','book__title')
The output is:
{'book__creators__name': 'Garczyński Stefan', 'book__creator__role': 'T', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
{'book__creators__name': 'Święcka Teresa', 'book__creator__role': 'T', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
{'book__creators__name': 'Beard Charles', 'book__creator__role': 'A', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
{'book__creators__name': 'Beard Mary', 'book__creator__role': 'A', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
by author:
book = Copy.objects.filter(book__creators__name='Beard Mary', book__creator__role='A').values('book__creators__name', 'book__creator__role', 'book__title')
the output:
{'book__creators__name': 'Beard Mary', 'book__creator__role': 'A', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
The view and template are just a draft, it doesn't work yet. I' trying first to find a solution in the shell.
the view:
class Searchg(View):
def get(self, request, *args, **kwargs):
form = SearchForm()
return render(request, 'working/search.html', {'form': form})
def post(self, request, *args, **kwargs):
form = SearchForm(request.POST)
if form.is_valid():
author = form.cleaned_data['author']
title = form.cleaned_data['title']
if author and title:
books = Book.objects.filter(creators__name__icontains=author,
creator__role='A', title__icontains=title).order_by('title')
print('a ' , len(books))
elif author:
books = Book.objects.filter(creators__name__icontains=author, creator__role='A').order_by('title')
print('b ' , len(books))
elif title:
books = Book.objects.filter(title__icontains=title).order_by('creators__name')
print('c ' , len(books))
else:
print("Please enter search criteria")
books = books.values('creators__name', 'title').annotate(title_count=Count('title'))
paginator = Paginator(books, 25)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
cache.set('books', books)
return redirect(reverse('working:book_list'))
and template:
{% if page_obj %}
<table class="table table-bordered">
<thead>
<tr>
<th scope="col"></th>
<th scope="col"> Liczba egzemplarzy</th>
<th scope="col">Tytuł</th>
<th scope="col">Autor</th>
</tr>
</thead>
<tbody>
<tr>
{% for book in page_obj %}
<th scope="row">{{ forloop.counter0|add:page_obj.start_index}}</th>
<td>{{ book.title_count }}</td>
<td><em> {{ book.title }}</em></td>
<td> {{ book.creators__name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<button class="btn btn-light">« first</button>
<button class="btn btn-light">previous </button>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<button class="btn btn-light">next</button>
<button class="btn btn-light">last »</button>
{% endif %}
This is one of the (many) reasons why using .values(…) [Django-doc] is often not a good idea. One typically uses this to group on a special field, etc. Not to obtain values.
Furtherdmore you probably want to query on the Book model, not on the Copy model, since that will make it harder to render each book with one record. You thus can filter with:
from django.db.models import Count
books = Book.objects.filter(
creator__name='Beard Mary',
creator__role='A'
).annotate(
ncopies=Count('copy')
).prefetch_related('creator_set')
and then in the template, you can render this for example with:
{% for book in books %}
{{ book.title }}: {{ book.ncopies }} copies
{% for creator in book.creator_set.all %}
{{ creator.person.name }}: {{ creator.role }}
{% endfor %}
{% endfor %}
Here the .creator_set will not take the filtering of the QuerySet into account, since this is handled in a separated query.
You can also query on the book.copy_set.all in the template, to retrieve all related Copy objects.

joined tables lost 'id' attribute

I'm trying to join two tables to be able to relate the corresponding 'theme' to a 'topic'. The join seems to work, but the template gives this error when rendering:
jinja2.exceptions.UndefinedError: 'sqlalchemy.util._collections.result
object' has no attribute 'id'
How can I address Topic.id after joining the tables?
models
class Topic(db.Model):
id = db.Column(db.Integer, primary_key=True)
topic_name = db.Column(db.String(64))
opinions = db.relationship(Opinion, backref='topic')
theme_id = db.Column(db.Integer, db.ForeignKey('theme.id'))
class Theme(db.Model):
id = db.Column(db.Integer, primary_key=True)
theme_name = db.Column(db.String(64))
topics = db.relationship(Topic, backref='theme')
view
#main.route('/topics', methods=['GET', 'POST'])
def topics():
topics = db.session.query(Topic, Theme).join(Theme).order_by(Theme.theme_name).all()
themes = Theme.query
form = TopicForm()
form.theme.choices = [(t.id, t.theme_name) for t in Theme.query.order_by('theme_name')]
if form.validate_on_submit():
topic = Topic(topic_name=form.topic_name.data,
theme_id=form.theme.data)
db.session.add(topic)
return render_template('topics.html', topics=topics, themes=themes, form=form)
html jinja2 template
<table class="table table-hover parties">
<thead><tr><th>Theme</th><th>#</th><th>Name</th><th>Delete</th></tr></thead>
{% for topic in topics %}
<tr>
<td>{{ topic.theme_id }}</td>
<td>{{ topic.id }}</td>
<td>{{ topic.topic_name }}<span class="badge">0</span></td>
<td><a class="btn btn-danger btn-xs" href="{{ url_for('main.delete_topic', id=topic.id) }}" role="button">Delete</a></td>
</tr>
{% endfor %}
</table>
Alter your query to be:
topics = db.session.query(Topic).join(Theme).order_by(Theme.theme_name).all()
Using query(Topic) indicates we are interested in getting Topic values back. In contrast, your current implementation uses query(Topic, Theme) which indicates you are interested in getting tuples of (Topic, Theme).