Django: Confusion with accessing database model's foreign key data - python

This is my first time working with Django and while working I have encountered with a confusion to create a particular statement in views that leads to my desired output. I have created a model 'Parents' which has data of a specific student (Foreign Key), and I am confused to access that student id for further process like working with Attendance, or Results of that specific student. Below are necessary codes and my trial to fetch data.
Models.py
class Students(models.Model):
id = models.AutoField(primary_key=True)
admin = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
gender = models.CharField(max_length=50)
address = models.TextField()
course_id = models.ForeignKey(Courses, on_delete=models.DO_NOTHING, default=1)
session_year_id = models.ForeignKey(SessionYearModel, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = models.Manager()
def __str__(self):
return self.admin.first_name + " " + self.admin.last_name
class Parents(models.Model):
id = models.AutoField(primary_key=True)
admin = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
gender = models.CharField(max_length=50)
**student = models.ForeignKey(Students, on_delete=models.CASCADE)**
relation = models.CharField(max_length=255)
address = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = models.Manager()
def __str__(self):
return self.admin.first_name + " " + self.admin.last_name
Here I have two models, Students model has all information regarding student and the other model is Parent model which has parent information with its specific student id.
Below is the views file code where I am trying to fetch student id of currently logged in parent,
def HOME(request):
stud_data = Parents.objects.filter(student__id = request.user.id)
print(stud_data)
return None
At the time of page reload, I am able to get an empty QuerySet[] as it is not able to find the id.
Kindly help me finding the solution to this problem, so that I can continue thinking about the development.
Thanks :)

As you mentioned here, you are looking for Student data for currently logged in Parent. Hence you can look for Student data directly from Parent object. Like this:
stud_object = request.user.parent.student
This relation works because Parent has a OneToOne relation with CustomUser (I assume Authentication's custom User model), hence you should get Parent object from request.user.parent (reverse relation for OneToOne). Also, student field is a ForeignKey of Parent model.
Addionally, I think the relation between Parent and Student should be ManyToMany, because a Student can have multiple parents and one parent can have multiple students (or children).

There are two possibilities:
The View code that you have attached should be returning stud_data not None, but I am assuming that you know this and this current state of the code is just for debugging purposes.
The request.user.id contains a value that doesn't belong to any student's ID in the database. As you are using filter, it's not going to complain about it and just return you an empty QuerySet. I'd suggest using the get() filter here which raises the DoesNotExist exception and would help in debugging as well.
def home(request):
stud_data = Parents.objects.get(student__id = request.user.id)
return stud_data
Hope it helps!
Best of luck with your new journey!

Related

Django admin site: Unable to lookup 'child model' on 'parent model or 'parent modelAdmin'

This one's really difficult to write as a question since I'm not exactly sure how to ask this in the first place. But I'll try. Basically, I'm experiencing an AttributeError when I'm visiting the parent model in my django admin site. My current django database has 2 tables (except the prebuilt tables): a parent table / model for activity, and a child table / model for instruction. This is the structure of my models:
class activity(models.Model):
title = models.CharField(max_length=30,default=None)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User,on_delete=models.CASCADE,default=None)
def __str__(self):
return self.__class__.__name__
class instruction(models.Model):
detail = models.CharField(max_length=50,default=None)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User,on_delete=models.CASCADE,default=None)
activity = models.ForeignKey(activity,on_delete=models.CASCADE,default=None)
def __str__(self):
return self.__class__.__name__
So, if I add a new activity in the django admin site, say: Push-ups, then, I should be able to select this newly added parent record in the instruction form. However, when I try to add a new instruction record and select a saved activity record, the activity dropdown shows the name of the model only(in this case, activity). It doesn't show Push-ups. What I did next was to add some modelAdmins for the activity and instruction. Here's the code:
class activityAdmin(admin.ModelAdmin):
list_display = [field.name for field in activity._meta.get_fields()]
class instructionAdmin(admin.ModelAdmin):
list_display = [field.name for field in instruction._meta.get_fields()]
admin.site.register(activity, activityAdmin)
admin.site.register(instruction, instructionAdmin)
However, when I visit the activity page of the admin site this time, the page throws an AttributeError exception with the value Unable to lookup 'instruction' on activity or activityAdmin. This does not happen on the instruction page. I realized that this may not be the method to display the activity's title in the instruction form. Still, I need to add the modelAdmin in the admin.py in order to display all the fields of each models in the admin site. So in summary:
1. I need to display the parent model's field value (activity title) as an option in the child model's dropdown and not only the name of the parent model
2. I need to display the fields of each models on their respective registries in the admin site
Try updating your str definition. This is used as the dropdown display value and you are currently setting to the class name. If you want it to show the title in the dropdown:
class activity(models.Model):
title = models.CharField(max_length=30,default=None)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User,on_delete=models.CASCADE,default=None)
def __str__(self):
return self.title

Sort by aggregated foreign field in Django ModelAdmin changelist

In my current project I am trying to set up a simple testing app in Django. For management I use the generated Django admin, but I struggle to include a sortable computed field with best test result in changelist view of a model.
My models are as follows (simplified):
class Candidate(models.Model):
name = models.CharField(max_length=255, null=False)
email = models.EmailField(unique=True, null=False)
class Test(models.Model):
candidate = models.ForeignKey(Candidate, on_delete=models.CASCADE, null=False)
result = models.PositiveIntegerField(null=True)
class Question(models.Model):
text = models.TextField(null=False)
correct_answer = models.CharField(max_length=1, choices=OPTIONS, null=False)
class Answer(models.Model):
test = models.ForeignKey(Test, on_delete=models.CASCADE)
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='answers')
answer = models.CharField(max_length=1, choices=Question.OPTIONS, null=True)
A candidate may have multiple tests and I want to display a field with his best result in the changelist view and be able to sort by it. The result is a percentage of correct answers (Answer.question.correct_answer == Answer.answer) out of all answers with the same test FK.
Discovered I cannot use a custom computed field defined by a function, because Django then cannot sort by it as sorting needs modification of a queryset which translates directly to SQL. So I added the Test.result field with calculated percentages (which denormalized the scheme :-/ ) and try to add annotated field in queryset with SELECT MAX(Test.result) FROM Test WHERE Test.candidate = {candidate} for every candidate, but cannot find a way how to do it.
The problem is, that the query needs reversed foreign key mapping, because of the 1:M mapping of candidate:test and I haven't found a way how to implement it. This is as far as I got:
class CandidateAdmin(admin.ModelAdmin):
list_display = ['name', 'email','best_result']
search_fields = ['name', 'email']
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_best_result = models.Max('tests_result')
)
return queryset
def best_result(self, obj):
return obj._best_result
But Django doesn't understand my attempt use MAX on reversed foreign key search of tests_result. Could you advise? Or if I missed a way how to add custom sorting, so I don't need to keep the calculated test result in the database while still sorting by it, I'd be grateful for any hint.
In the end I created a database view with the query, added it to my models with managed = False in Meta and used that instead. Works like a charm.

Check the results of one queryset against the results of another queryset django

In my django application I have three models, People, Reviews and File.
class People(models.Model):
firstname = models.charField(max_length=20)
lastname = models.charField(max_length=20)
class Reviews(models.Model):
STATUSES = (
('pending', 'Pending'),
('approved', 'Approved'),
('rejected', 'Rejected')
)
person = models.OneToOneField(People, on_delete=models.CASCADE, primary_key=True)
status = models.CharField(max_length=10, choices=STATUSES, default='pending')
comment = models.TextField()
class File(models.Model):
owner = models.OneToOneField(Reviews, on_delete=models.CASCADE, primary_key=True)
comment = models.TextField()
issue_date = models.DateField(auto_now_add=True)
See that OneToOneField on the File model? I need to be able to filter that dropdown, based on two conditions.
It needs to only display records from the Reviews model that have a status of approved.
Reviews.objects.filter(status="approved")
The displayed results must not already exist in the File model's records.
File.objects.all()
And also, while creating an instance of the File model, when a value is selected from the dropdown, how could I automatically populate the comment field with the value from the Review model's comment field?
I can't quite figure it out.
I think you can try like this:
Reviews.objects.filter(status="approved", file__isnull=True)
Here due to OneToOne relationship, all reviews object has a file attribute which returns a File Object. I am using isnull to check if it is empty or not.
Update
IMHO, I don't see why the value of the comment should be stored twice in the Database. If you have the comment value in Review, then you can access it like this:
file = File.objects.first()
comment = file.review.comment
Now, when it comes to showing it in admin site while creating File Instance, its not possible to do that without using Javascript. But another hackish approach is to display that value in the review dropdown. But for that, you need to override the __str__ method of Review Model. Like this:
class Reviews(models.Model):
STATUSES = (
('pending', 'Pending'),
('approved', 'Approved'),
('rejected', 'Rejected')
)
person = models.OneToOneField(People, on_delete=models.CASCADE, primary_key=True)
status = models.CharField(max_length=10, choices=STATUSES, default='pending')
comment = models.TextField()
def __str__(self):
return "{}: {}".format(self.status, self.comment)
But, if a file instance is created, or if you want to show comment in the file list in admin site, you can do that like this:
from django.contrib import admin
class FileAdmin(admin.ModelAdmin):
list_display = ('issue_date', 'comment')
def comment(self, obj):
return obj.review.comment
admin.site.register(File, FileAdmin)

Using multiple columns as ForeignKey to return in another table

I'm new to Django so I make 3 simple tables to return a WishList. The thing is that I want whenever user asks for WishList, his/her user_id is used to make a SELECT query to return his/her own WishList. And I want to get product title and product url from my WishList table. I'm using to_field but with that way I only can get product title back. I don't know much about Django so help me!
Product
class Product(models.Model):
class Meta:
unique_together = (('id', 'title'),)
title = models.CharField(max_length=200, unique=True,
help_text='Name of the product')
url = models.CharField(max_length=300, default='',
help_text='Url of the product')
def __str__(self):
return 'Product: {}'.format(self.title)
WishList
class WishList(models.Model):
class Meta:
unique_together = (('user', 'product'),)
user = models.ForeignKey(fbuser,
on_delete=models.CASCADE,
help_text='Facebook user',
to_field='user_id')
product = models.ForeignKey(Product, to_field='title', db_column='title',
on_delete=models.CASCADE)
def __str__(self):
return 'WishList: {}'.format(self.user)
It's not a good practice to override to_field to another field different than your model.pk unless you have a really good reason and you know what you are doing (definitely not the case right now).
So after you read the docs, you will know that in order to get wishlisht related to a user, you can use the ForeignKey reverse relation to get all related wishlists for a user.
user_wishlists = my_user.wishlist_set.all()
#Because we know that you want to access the wishlist.products
#in order to optimize things (in terms of db queries)
#you can add and .select_related('product')
#e.g, user_wishlists = my_user.wishlist_set.all().select_related('product')
#now follow the wishlist.product foreign key to access the related product for every wishlist
for wishlist in user_wishlists:
product = wishlist.product
print (product.id, product.title, product.url)
Now after you read a little bit more of the documentation
you will notice that your WishList model is in fact an intermediate model for a ManyToMany relation between User and his wished products, then you will know that you can define a M2M field between user and products via WishList like so:
class FbUser(models.Model):
#...
wished_products = models.ManyToManyField(
Product,
through='WishList',
through_fields=('user', 'product')
)
#and now accessing user wished products would be easy as:
user_wished_products = my_user.wished_products.all()
for product in user_wished_products:
print (product.id, product.title, product.url)

letting only the creator edit a Category in django

In my web app,I have a m2m relation between a Category and a User
class Category(models.Model):
users = models.ManyToManyField(User,related_name='category_users')
title = models.CharField(max_length=200)
description = models.TextField()
When a user tries to create a Category,I do this
category,created = Category.objects.get_or_create(title__iexact=category_title,defaults={'title':category_title,'description':category_title})
I need to provide the user with an edit page for Category.I thought to provide it such that only the person who created the category may edit the description.
So,I tried to add a creator field in the model.
class Category(models.Model):
users = models.ManyToManyField(User,related_name='category_users')
title = models.CharField(max_length=200)
description = models.TextField()
creator = models.ForeignKey(User,related_name='category_creator')
However,this causes an IntegrityError when get_or_create() method is run,because creator_id is null
...
category_title = category_title_form.cleaned_data['title']
category_title = category_title.strip()
if len(category_title)>0:
category,created = Category.objects.get_or_create(title__iexact=category_title,defaults={'title':category_title,'description':category_title})
if request.user not in category.users.all():
category.users.add(request.user)
category.save()
if created:
category.creator = request.user
category.save()
Can someone tell me if there is a way I can solve this?
When a user creates a category, you can add that category to the users "Category Set" --
category, created = user.category_set.get_or_create(
title__iexact=category_title,
defaults={'title': category_title, 'description': category_title}
)
By accessing the category table through user.category_set, the user_id field will be automatically maintained.
Only the user's categories can be retrieved through this call, and if it creates a new category, then the user_id will be set automatically.

Categories

Resources