Django ORM fetch data by 2 levels of foreign key relation - python

I have the following models:
class Project(models.Model):
name = models.CharField(max_length=300, unique=True)
description = models.CharField(max_length=2000)
class TemporaryUser(models.Model):
username = models.CharField(max_length=400)
project = models.ForeignKey(
Project,
on_delete=models.CASCADE,
related_name='users'
)
class QuestionSession(models.Model):
project = models.ForeignKey(
Project,
on_delete=models.CASCADE,
related_name='sessions',
blank=True,
null=True,
default=None
)
class Question(models.Model):
# stores the main json object with all required information
description = JSONField(
max_length=10000, blank=True, null=True, default=None
)
question_session = models.ForeignKey(
QuestionSession,
on_delete=models.CASCADE,
related_name='questions',
blank=True,
null=True,
default=None
)
class Answer(models.Model):
question = models.ForeignKey(
Question,
related_name='answers_list',
on_delete=models.CASCADE)
answer = models.CharField(max_length=500)
answered_by = models.ForeignKey(
TemporaryUser,
on_delete=models.CASCADE,
related_name='answers',
blank=True,
null=True,
default=None
)
In a nutshell, my app contains questions, session is a collection of questions, and a project is a collection of sessions. All users are unique per project.
I can fetch all users and all answers within a specific project with the following:
TemporaryUser.objects.all().filter(project__id=project_id)
How can I do the same within a session? I don't really know how to do it, I need to filter users by session, is there a way how to do it with my relations?

Do you mean like:
TemporaryUser.objects.filter(project__sessions__id=id)

I think its cleaner to use reverse relation here:
session = QuestionSession.objects.first()
session.project.users.all()
# as User model has a FK with Project , and it has related_name="users"
You can use this in your template as well:
{% for qs in questionsessions %} // questionsessions is the queryset of QuestionSession
{% for user in qs.project.users.all %}
{{ user.username }}
{% endfor %}
{% endfor %}

If you want to fetch the users with only one query, the way to go is:
users = TemporaryUser.objects.filter(project__sessions=id)
However, if you want to fetch more data related to that session, maybe you should consider to fetch from the session itself (see Following relationships backward). Be cautious, as the number of queries to the database is not optimized.
session = Session.objects.get(pk=id)
users = session.project.users.all()
questions = session.questions.all()
You can use select_related and prefetch_related if you want to make less queries. This could be very important if you are interested in fetching data for a list of sessions and not only one.

Related

display objects from inline model in DetailView

I have 2 models Product and Resource. Resource has to be a TabularInline model in my admin panel. I am struggling with filtering resource titles that are related only to this product. Since it is a ForeignKey I should use select_related but I am not sure how to use it in my case. For now, the loop in my HTML file gives me all sales files (from all products).
models.py
class Product(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField('title', max_length=400, default='')
slug = models.SlugField(unique=True, blank=True, max_length=600)
class Resource(models.Model):
id = models.AutoField(primary_key=True)
type = models.CharField(max_length=32, choices=RESOURCE_TYPE, default='sales')
title = models.CharField(max_length=400, blank=False, null=False, default='')
related_files = models.FileField(upload_to='recources/', null=True, blank=True)
publish = models.DateTimeField('Published on', default=timezone.now)
resources = models.ForeignKey(Product, default='', on_delete=models.PROTECT, null=True, related_name='resources')
admin.py
class Resourceinline(admin.TabularInline):
model = Resource
class ProductAdmin(ImportExportModelAdmin):
inlines = [
Resourceinline,
]
resource_class = ProductResource
admin.site.register(Product, ProductAdmin)
views.py
class ProductDetailView(DetailView):
template_name = 'ProductDetailView.html'
model = Product
def get_context_data(self, **kwargs):
context = super(ProductDetailView, self).get_context_data(**kwargs)
resources_sales = Resource.objects.select_related('resources').filter(resources_id =1, type='sales') # not sure what to put here
context['resources_sales'] = resources_sales
return context
ProductDetailView.html
{% for resource in resources_sales.all %}
<p>{{resource.title}}</p>
{% endfor %}
Question
Where am I making the mistake and how can I display resource objects that are related to type=sales and are related only to this product in DetailView.
Edit
I realized that there is a column named resources_id that is connecting both models. Now I am struggling to filter it by id of current DetailView. I put resources_id=1 in my views.py but it must relate to DetailView that user is currently looking at. I tied to put resources_id=self.kwargs['id'] but it gives me KeyError at /product/test-product/ 'id' How can I do that?
since you are using generic DetailView you can refer to the current object with self.get_object(). actually that return the single object that view display. however you can use instateself.object too.
so you can filter the Product related Resources using Resource.objects.filter(resources=self.get_object(), type='sales')
you can read more Single object mixins

NameError: name *model* is not defined; ManyToManyField/ForeignKey

Here is a relationship I'm aiming for in terms of a User, Question, Bookmark relationship; Bookmark being an intermediary table:
A user can bookmark many Questions (topic pages)
A Question (topic page) can be bookmarked by several users
The keyword here being bookmark(ed), I have created a Bookmark model to show this relationship. However there's a problem of trying to make migrations due to a NameError being raised. Depending where they are defined in the script it's raising either:
NameError: name 'Question' is not defined
NameError: name 'Bookmark' is not defined
How can I get past this error in order to push the Bookmark into the migrations directory with its ForeignKey references?
class Question(models.Model):
title = models.CharField(unique=True, max_length=40)
body = models.TextField()
created = models.DateField(auto_now_add=True)
likes = models.IntegerField(default=0)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True
)
views = models.ManyToManyField(
View,
related_name="+"
)
tags = models.ManyToManyField(
Tag,
related_name="questions"
)
bookmarks = models.ManyToManyField(
Bookmark,
related_name="+",
)
def __str__(self):
return self.title
class Bookmark(models.Model):
question = models.ForeignKey(
Question, on_delete=models.CASCADE,
related_name="+"
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name="bookmarks"
)
Pass the related model as a string to the field constructor:
bookmarks = models.ManyToManyField(
'Bookmark',
related_name="+",
)
Django looks up the model class only when it's necessary, so it can support recursive foreign key shenanigans.
From the Django documentation:
If you need to create a relationship on a model that has not yet been defined, you can use the name of the model, rather than the model object itself.

Get site-specific user profile fields from user-created object

I am using Django sites framework (Django 2.1) to split an app into multiple sites. All of my models except the User model are site-specific. Here is my Post model:
post.py
class Post(models.Model):
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
related_name='children',
related_query_name='child',
blank=True,
null=True,
)
title = models.CharField(
max_length=255,
blank=True,
)
body_raw = models.TextField()
body_html = models.TextField(blank=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
on_site = CurrentSiteManager()
I have no problem separating posts out by site. When I want to get the posts, I call:
posts = Post.on_site.filter(...)
I have a separate model called UserProfile. It is a many-to-one profile where there is a unique profile created for each user-site combination (similar to profile implementation at SE). The profile has a reputation attribute that I want to access when I get any post. This reputation attribute should be different for each site (like how on SE you have different rep on each site you are a member of).
user_profile.py
class UserProfile(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
reputation = models.PositiveIntegerField(default=1)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
on_site = CurrentSiteManager()
How do I access the user's username (on the User model) as well as the user's reputation (on the UserProfile model) when I get Posts from a query?
I'd like to do something like:
Post.on_site.filter(...)
.select_related('user__userprofile')
.filter_related(user__userprofile.site == get_current_site())
How do I filter a Many-To-One related model?
Better to make UserProfile -> User relationship to be OnetoOne,
because Django doesn't know which of many profiles to show
(but you also need to define related_name)
models.OneToOneField(get_user_model(), related_name='userprofile_rev')
Then you will be able to do this
qs = Post.on_site.filer().select_related('user', 'user__userprofile_rev')
for post in qs:
print(post.user.username, post.user.userprofile_rev.reputation)
If you don't want to change your DB structure you can do like this
(but you need to specify which profile to return)
qs = Post.on_site.filer().select_related('user').prefetch_related('user__userprofile_set')
for post in qs:
print(post.user.username, post.user.userprofile_set[0].reputation)

Delete a post in Django

I new to django and I am trying to make a web application. I have this page page image and I want to delete one post when I press the delete button. How can I do that? This is my modal for 'Post' :
class Post(models.Model):
created_date = models.DateTimeField()
title = models.CharField(max_length=100)
profile_image = models.ImageField(upload_to='poze', blank=True, null=True)
text = models.CharField(max_length=1000, default='Nimic', blank=True)
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
I've been looking for delete methods, but I've always found form-only methods and I don't use form. Thank you.
In you html :
Delete
assuming that you are using a for loop :
{% for p in posts %}
In your urls :
path('delete/<post_id>',views.delete_post,name='delete')
In your views :
def delete_post(request,post_id=None):
post_to_delete=Post.objects.get(id=post_id)
post_to_delete.delete()
return HttpResponseRedirect(#name of the view function that returns your posts page)
And that's it
EDIT
This method deletes data from your database directly. So I recommend you add the #login_required decorator to your delete_post view function to protect your post. You can also make it accessible only for admin users or post owners in your html (Example : only users who have staff role can see the delete link)
{% if user.is_staff %}
<a ...>Delete</a>
{% endif %}
You need to pass argument with post id. It would like something like this
p = Post.objects.get(pk=2)
p.delete()
You should create a new field in the database table. Whenever you are deleting the data then you should change the delete field.
class Post(models.Model):
created_date = models.DateTimeField()
title = models.CharField(max_length=100)
profile_image = models.ImageField(upload_to='poze', blank=True, null=True)
text = models.CharField(max_length=1000, default='Nimic', blank=True)
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
delete_flag = models.BooleanField(default=False)
And whenever you are should the data then you should filter the data on the delete flag
Post.objects.filter(delete_flag=False)
Though it is subjective and I don't know you use case, but still as a beginner its better to start with this practice.
Read More
Article 2

Django Models Relationship Confusions

I have the following models:
class UserPost(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
class User(AbstractUser):
MALE = 'M'
FEMALE = 'F'
GENDER_CHOICES = (
(MALE, 'Male'),
(FEMALE, 'Female')
)
posts = models.ManyToManyField(Post, through='UserPost')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Post(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField()
status = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
When I run python manage.py makemigrations, it raises the following error:
users.User.posts: (fields.E303) Reverse query name for 'User.posts' clashes with field name 'Post.user'.
HINT: Rename field 'Post.user', or add/change a related_name argument to the definition for field 'User.posts'.
There is a many-to-many relationship between User and Post models. Each user can like many posts and each post can be liked by many users.
There is also a many-to-one relationship between User and Post models. Each user can write many posts and each post can be written by only one user.
Shouldn't reverse query name for 'User.posts' be user_set by default. If so, why is this name clashing with field name 'Post.user'? Can someone explain the meaning of this error? Thanks.
Do you need the UserPost model? It looks to have all the same fields as Post, and if you're after efficient querying, Django automatically creates database indexes on foreign keys. Here's a simple setup that should work pretty well:
class User(AbstractUser):
# Your fields go here, but you might not need the posts field
class Post(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='posts')
This would let you do a user.posts.all() to get all of the Post instances that belong to that user.

Categories

Resources