I have a model where one field is a ForeignKey so that each child object is linked to a parent object.
In my (jinja2) templates, I list some attributes from a subset of objects from the child model, including one of the parents' attributes. The page loads very slowly, so I am wondering if there is a faster way to do the following:
views.py
class TransactionView(LoginRequiredMixin, ListView):
model = Transactions
context_object_name = 'transaction_list'
template_name = 'bank/transactions.html'
def get_queryset(self):
return Transactions.objects.filter(owner_id=self.request.user)
template.html
<tbody>
{% for transaction in transaction_list %}
<tr>
<td>{{transaction.source_document.service_provider}}</td>
<td>{{transaction.account}}</td>
<td>{{transaction.tnsx_date}}</td>
<td>{{transaction.end_bal}}</td>
<td>{{transaction.amount}}</td>
<td>{{transaction.category}}</td>
</tr>
{% endfor %}
</tbody>
models.py
class Transactions(models.Model):
def __str__(self):
return str(self.tnsx_uuid)
owner = models.ForeignKey(
User,
on_delete=models.CASCADE,
db_index=True,
editable=True,
)
source_document = models.ForeignKey(
Document,
on_delete=models.CASCADE,
editable=True,
)
tnsx_uuid = models.UUIDField(default=uuid.uuid4, unique=True)
account = IBANField(enforce_database_constraint=True)
currency = models.CharField(max_length=4, blank=False, null=False)
currency_assumed = models.BooleanField(null=False)
<etc>
As iklinac suggested, using .prefetch_related() increases the speed of the query dramatically.
change views.py to the following:
class TransactionView(LoginRequiredMixin, ListView):
model = Transactions
context_object_name = 'transaction_list'
template_name = 'bank/transactions.html'
def get_queryset(self):
return Transactions.objects.filter(owner_id=self.request.user).prefetch_related('source_document')
Related
I have a profile, and in this profile I want to display bookmarks for all messages (this is my IntegerField). In other words, how many people have bookmarked a particular author's posts.
models.py
class Post(models.Model):
slug = models.SlugField(unique=True)
title = models.CharField(max_length=255, db_index=True)
author = models.ForeignKey(
"users.CustomUser", on_delete=models.SET_NULL, null=True, db_index=True
)
bookmarkscount = models.IntegerField(null=True, blank=True, default=0)
class Profile(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
This is my try in template but it does not work
<p>Bookmark</p>
<p>{{posts.bookmarkscount}}</p>
But work only if I use "for"
{% for post in posts %}
<p>{{ post.bookmarkscount}}</p>
{% endfor %}
views.py
class ProfileDetailView(DetailView):
model = Profile
template_name = "users/profile/profile.html"
def get_context_data(self, **kwargs):
try:
context["posts"] = Post.objects.filter(
author=self.object.user.is_authenticated
)
except Post.DoesNotExist:
context["posts"] = None
posts is a QuerySet type, a representation of the query to be sent to the DB. More like a list on steroids rather than a single instance of Post. This is a crucial concept you need to understand before coding anything in Django. (Docs here)
In order to get a sum of all the bookmarkscount values from all posts of a user, you need to use aggregation. (Docs here)
from django.db.models import Sum
posts.aggregate(Sum('bookmarkscount'))
# returns i.e.: {'bookmarkscount': 234}
I am trying to display data from two different models. The two models have a one-to-many relationship but I am not sure why it is not displaying the data from the MembersPresent model. Here are my models
and view
class fly_minute(models.Model):
mode = (
('Email', 'Email'),
('Zoom', 'Zoom'),
('Alternative', 'Alternative'),
)
mode_of_meeting = models.CharField(max_length=100, choices=mode, blank=False, )
date = models.DateField()
Time = models.TimeField()
minute_prepared_by = models.CharField(max_length=25)
location = models.CharField(max_length=25)
authorize_by = models.CharField(max_length=25)
item = models.TextField()
owner = models.CharField(max_length=25)
def __str__(self):
return self.mode_of_meeting
class MembersPresent(models.Model):
flyminute = models.ForeignKey(fly_minute, on_delete=models.CASCADE)
name = models.CharField(max_length=25)
status = models.CharField(max_length=25)
email = models.EmailField(max_length=25)
phone = models.IntegerField()
def __str__(self):
return self.name
#login_required(login_url='login_page')
def MinutesReport(request, minute_id):
report = fly_minute.objects.filter(id=minute_id)
return render(request, 'minute_report.html', locals())
{%for rpt in report%}
<tbody>
<tr class="table-active">
<td>{{rpt.flyminute.name}}</td>
<td>{{rpt.flyminute.status}}</td>
<td>{{rpt.flyminute.email}}</td>
<td>{{rpt.flyminute.phone}}</td>
</tbody>
{%endfor%}
You need to use 'prefetch_related()'
Returns a QuerySet that will automatically retrieve, in a single batch, related objects for each of the specified lookups.
This has a similar purpose to select_related, in that both are designed to stop the deluge of database queries that is caused by accessing related objects, but the strategy is quite different.
models.py
class MembersPresent(models.Model):
flyminute = models.ForeignKey(
fly_minute,
related_name='memberspresent_set', # this will be the default name, incase of need of change provide a new one
on_delete=models.CASCADE
)
...
views.py
#login_required(login_url='login_page')
def MinutesReport(request, minute_id):
report = fly_minute.objects.filter(id=minute_id).prefetch_related('memberpresent_set')
return render(request, 'minute_report.html', locals())
For further control, you can use Prefetch along with prefetch_related()
Note:
locals() returns a dict of all the locally defined variables in the current scope and this may result in exposing sensitive info. Instead of that create a custom dict and return it.
...
context = {
'report': report,
...
}
return render(request, 'minute_report.html', context)
You are referencing and querying in the opposite way. Your fly_minute model doesn't contain MembersPresent model as the Foreign Key. You are filtering the fly_minute model and trying to access MembersPresent model. So, you should change your fly_minute to in this way:
class fly_minute(models.Model):
mode = (
('Email', 'Email'),
('Zoom', 'Zoom'),
('Alternative', 'Alternative'),
)
mode_of_meeting = models.CharField(max_length=100, choices=mode, blank=False, )
date = models.DateField()
Time = models.TimeField()
minute_prepared_by = models.CharField(max_length=25)
location = models.CharField(max_length=25)
authorize_by = models.CharField(max_length=25)
item = models.TextField()
owner = models.CharField(max_length=25)
member = models.ForeignKey(MembersPresent, on_delete=models.CASCADE)#add this line and remove fly_minute in MembersPresent model
def __str__(self):
return self.mode_of_meeting
Then in template file change it to in this way:
{%for rpt in report%}
<tbody>
<tr class="table-active">
<td>{{rpt.member.name}}</td>
<td>{{rpt.member.status}}</td>
<td>{{rpt.member.email}}</td>
<td>{{rpt.member.phone}}</td>
</tbody>
{%endfor%}
I guess it works
Thanks, I ended up using fetch-related and had to make adjustments to my template.
<tr>
<th>Name</th>
<th>Status</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
{%for rpt in report%}
{%for rpts in rpt.memberspresent_set.all%}
<tbody>
<tr class="table-active">
<td>{{rpts.name}}</td>
<td>{{rpts.status}}</td>
<td>{{rpts.email}}</td>
<td>{{rpts.phone}}</td>
</tbody>
{%endfor%}
{%endfor%}
I have two model one model is being used for storing the blog posts and another model is being used for taking the ratings and comments. Below are two my models
# Models Code
class Products(models.Model):
name = models.CharField(max_length=50)
img = models.ImageField(upload_to='productImage')
CATEGORY = (
('Snacks','Snacks'),
('Juice','Juice'),
)
category = models.CharField(max_length=50, choices=CATEGORY)
description = models.TextField()
price = models.FloatField()
review = models.TextField()
# Rating Model
class Rating(models.Model):
product = models.ForeignKey(Products, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
stars = models.IntegerField(validators=[MinValueValidator(1),MaxValueValidator(5)])
comment = models.TextField()
#Views Code
class ProductListView(ListView):
model = Products
template_name = 'products.html'
context_object_name ='Products'
class ProductDetailView(DetailView):
model = Products
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['Rating'] = Rating.objects.filter(self.product_id) # How can i get the comments only for that specific product?
return context
In details-view how should I filter to fetch the comments for that specific product only ?
no need to write separate context for that in ProductDetailView, you can do it as follows in templates
{% for rate in object.rating_set.all %}
{{ rate.comment }}
{% endfor %}
i want to display number of Users in that company for all companies. I am using User in-built Model.
UserProfile Model
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
user_company = models.ForeignKey(Company, on_delete=models.CASCADE)
Company Model
class Company(models.Model):
company_name = models.CharField(max_length=20, unique=True)
company_description = models.CharField(max_length=100)
View to display Companies
class CompanyListView(LoginRequiredMixin, generic.TemplateView):
template_name = 'company/company.html'
def get_context_data(self, **kwargs):
context = super(CompanyListView, self).get_context_data(**kwargs)
context['companies'] = Company.objects.exclude(company_name='Google')
# Count of Users
return context
Display Count of Users for each company in single template
First I'll add a related_name attribute to the user_company field:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
user_company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='user_profiles')
Then in your get_context_data method you just need to annotate() your queryset in the following way:
def get_context_data(self, **kwargs):
context = super(CompanyListView, self).get_context_data(**kwargs)
context['companies'] = Company.objects.exclude(
company_name='Google'
).annotate(
total_users=Count('user_profiles')
)
# Count of Users
return context
Note that you will need to import Count function as follows:
from django.db.models import Count
Then in your template you can access the count of users like follows:
{% for company in objects %}
{{ company.total_users }}
{% endfor %}
I have a Tourguide model with a many to many relationship with Places. Both models are defined as :
class Tourguide(models.Model):
id = models.AutoField(db_column='ID', primary_key=True, unique=True)
title = models.CharField(db_column='Title', max_length=255, blank=True)
places = models.ManyToManyField(Place, db_column='placesdj')
places_app = models.CharField(max_length=255, db_column='places')
created_on = models.DateTimeField(db_column='Created_On', default = now)
class Meta:
managed = False
db_table = 'tourguide'
def __str__(self):
return self.title
class Place(models.Model):
place_id = models.AutoField(db_column='ID', primary_key=True)
place_name = models.CharField(db_column='Place_Name', max_length=255)
address_line_1 = models.CharField(db_column='Address_Line_1', max_length=255)
address_line_2 = models.CharField(db_column='Address_Line_2', max_length=255)
area = models.CharField(db_column='Area', max_length=255)
class Meta:
managed = False
db_table = 'Place'
def __str__(self):
return self.place_name
THE PROBLEM
When I try to print the places in a tourguide using :
{% for place in tour.places.all %}
<tbody>
<td>{{ forloop.counter }}</td>
<td>id: {{place.place_id}}, {{ place.place_name }} </td>
<td> {{ place.description }} </td>
</tbody>
{% endfor %}
The order of the places is all random and not the same as the order I inputed it as. I want to print the places in the same order that I placed them in. My view for listing the tourguides and places within them is as so.
def tour_list(request, template_name='tourguides/tour_list.html'):
tourguide_list = Tourguide.objects.all()
paginator = Paginator(tourguide_list,6)
page = request.GET.get('page')
tourguides = paginator.get_page(page)
data = {}
data['tourguides'] = tourguides
return render(request, template_name, data)
Update
I have an array of place id's in the tourguide table, is there a way i can use that?
Relational databases do not sort rows by default, and since they are internally stored in all kinds of weird data structures, there is no "input order" either.
As a workaround, you can use the automatically generated place_id field as a sort key as it is pretty much guaranteed to go up as new entries are created. You can add a default sorting to your Place class, for example:
class Place(models.Model):
...
class Meta:
...
ordering = ('place_id',)
That would guarantee that any queries that will return a Place queryset will be ordered by place_id by default (i.e. unless you have an explicit .order_by() clause).