Django reduce queries - python

I have two models, Post and Vote. Users can upvote and downvote posts.
models.py:
class Post(models.Model):
poster = models.ForeignKey('auth.User')
question = models.ForeignKey('self', null=True, blank=True)
post_title = models.CharField(max_length=300)
post_content = models.TextField(null=True, blank=True)
is_published = models.BooleanField(default=True)
is_locked = models.BooleanField(default=False)
is_question = models.BooleanField(default=True)
is_deleted = models.BooleanField(default=False)
created_date = models.DateTimeField(
default=timezone.now)
published_date = models.DateTimeField(
blank=True, null=True)
date_modified = models.DateTimeField(
blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.post_title
class Vote(models.Model):
user = models.ForeignKey('auth.User')
post = models.ForeignKey('Post')
vote_type = models.SmallIntegerField()#-1, 0, 1
date_voted = models.DateTimeField(
default=timezone.now)
def __str__(self):
return self.user
I use the following code in my view to return the posts to templates:
views.py:
def index(request):
posts = Post.objects.filter(created_date__lte=timezone.now(
), is_question=1, is_published=1).order_by('-created_date')
#removed the paging stuff here for simplification
return render(request, 'homepage/index.html', {'posts': posts})
This just returns the posts, but I also want to check if the current user has voted or not (and possibly the sum of the vote_type column for each post which is the total number of votes for the post).
Currently I use template tags for each post to check if the current user has voted or not. This creates a lot of queries. (Currently 50 queries with 40 duplicates).
My index.html sample code:
{% for post in posts %}
{% if post|userVotedThisPost:request.user.id == 1 %}
<img id="upvote-img" class="icons" src="{% static 'img/upvote-marked.svg' %}" alt="Upvote">
{% else %}
<img id="upvote-img" class="icons" src="{% static 'img/upvote.svg' %}" alt="Upvote">
{% endif %}
{% endfor %}
Is there any way to query everything in the views.py and then in the template I could check like this: (if post.user_voted), so that the database is not hit each time in the for loop?

You can use prefetch_related to fetch the related votes for that user.
from django.db.models import Prefetch
Post.objects.filter(
created_date__lte=timezone.now(),
is_question=1,
is_published=1
).order_by(
'-created_date',
).prefetch_related(
Prefetch('vote_set', queryset=Vote.objects.filter(user=request.user), to_attr='user_votes')
)
Then in your template, change the check to:
{% if post.user_votes %}

Related

Uable to get the product name from the model

I've create 3 models for the Order processing. However, I couldn't show the product name on template for every single order. Does my for loop logic or 'get' method go wrong?
models.py:
class Product(models.Model):
product_name = models.CharField(max_length=200)
price = models.DecimalField(decimal_places=2, max_digits=10, blank=True)
created = models.DateTimeField(auto_now=True)
slug = models.SlugField(max_length=255, unique=True)
def __str__(self):
return self.product_name
class OrderItem(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
item = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
ordered = models.BooleanField(default=False)
def __str__(self):
return f"{self.quantity} of {self.item.product_name}"
class Order(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
items = models.ManyToManyField(OrderItem)
start_date = models.DateTimeField(auto_now_add=True)
ordered = models.BooleanField(default=False)
def __str__(self):
return self.user.username
view.py:
def user_info(request):
orders = Order.objects.filter(user=request.user, ordered=True).order_by('-start_date')
context = {
'orders': orders,
}
return render(request, 'home.html', context)
home.html:
{% for order_item in orders.items.all %}
<p>{{ order_item.item.product_name }}</p>
{% endfor %}
You are passing orders from your view which is a queryset so you can't do orders.items.all. You should do a for loop to loop over orders and then get the items:
{% for order_item in orders %}
<p>product names for order id {{order_itme.id}}:</p>
{% for item in order_item.items.all %}
<p>{{ item.item.product_name }}</p>
{% endfor %}
{% endfor %}
Note that this will renders all items for all orders. You should filter the result if you just need one or some of them.

Reverse not found with slug field

I am getting error while using get_absolute_url with slug field. Have tried few suggestions which are already exist in stack but didn't worked. Can anyone help me with this please.
Please refer this link for traceback.
models.py
Codes in models.
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1, on_delete=models.CASCADE)
draft = models.BooleanField(default=False)
publish = models.DateTimeField(auto_now=False, auto_now_add=False)
slug = models.SlugField(unique=True)
image = models.ImageField(upload_to=upload_location,
null=True,
blank=True,
width_field="width_field",
height_field="hieght_field")
hieght_field = models.IntegerField(default=0)
width_field = models.IntegerField(default=0)
content = models.TextField()
updates = models.DateTimeField(auto_now=True, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
def get_absolute_url(self):
return reverse('post:detail', kwargs={'slug':self.slug})
Views.py
codes in views.
def post_list(request):
queryset_list = Post.objects.active()
if request.user.is_staff or request.user.is_superuser:
queryset_list = Post.objects.all()
query = request.GET.get('q')
if query:
queryset_list = queryset.filter(
Q(title__icontains=query)
).distinct()
context = {
'object_list':queryset_list,
'posts': page,
"page_request_var": page_request_var,
}
return render(request, 'index.html', context)
urls.py
urls mapping.
urlpatterns = [
path('detail/<slug:slug>/', views.detail, name='detail'),
]
html page
code in index.html
{% for obj in object_list %}
<div class="container">
<p class="card-text">{{obj.content|linebreaks|truncatechars:120}}</p>
View
<!-- {{obj.title}} -->
</div>
{% endfor %}
Error was occurring because of commented line in index.html {{obj.title}} after removing this line page is getting loaded perfectly and one more thing 'pk' should be replaced by slug in detail view to work detail page. def detail(request,slug): #slug replaced pk

Django template if statement doesn't return what I expected

I want to allow users to edit their own comments. I tried to compare comment.nick as well as to request.user.username and to request.user.get_username. For example comment.nick return 'author' and request.user.username will return 'author', but Edit won't be visible.
{% if request.user.username == comment.nick %}
Edit
{% endif %}
# models.py
class Commentary(models.Model):
nick = models.ForeignKey('Profile', on_delete=models.CASCADE, max_length=20)
comment = models.TextField(max_length=300)
article = models.ForeignKey(Article, on_delete=models.CASCADE)
added = models.DateTimeField(auto_now_add=True, blank=True)

Calling a foreign key relationship in an "if" statement in Django template

I have two models, Booking and Confirmation that are related via a ForeignKey relationship through "booking." I want to only display bookings in my detail view that have an attribute value of is_confirmed ==True. I don't really want a queryset, I just want to display the booking information from the "Booking" model if the confirmation is True in the template.
models.py:
class Booking(models.Model):
user = models.ForeignKey(CustomUser, null=True, default='', on_delete=models.CASCADE)
expert = models.ForeignKey(CustomUser, null=True, default='',on_delete=models.CASCADE, related_name='bookings')
title = models.CharField(max_length=200, default='Video call with ..', null=True)
start_time = models.DateTimeField('Start time', null=True)
end_time = models.DateTimeField('End time', null=True)
notes = models.TextField('Notes', blank=True, null=True)
class Meta:
verbose_name = 'Booking'
verbose_name_plural = 'Bookings'
def get_absolute_url(self):
return reverse('booking:booking_detail', kwargs={"pk": self.pk})
class Confirmation(models.Model):
booking = models.ForeignKey(Booking, on_delete=models.CASCADE)
expert_confirming = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
is_confirmed = models.BooleanField(default=False)
def get_absolute_url(self):
return reverse('booking:booking_detail', kwargs={"pk": self.booking_id})
views.py:
class BookingDetailView(DetailView):
model = Booking
template = 'templates/booking_detail.html'
booking_detail.html:
<div class="container" id="booking_content">
<p>{{ booking.title }}</p>
<p>{{ booking.start_time }}</p>
<p>Booking request by: {{ booking.user }}</p>
<p>Expert requested: {{ booking.expert }}</p></div>
I'm not sure how the if statement in the template should reference these related models to display what I want.
I think that with the way you have your models you would have to run a query on the Confirmation model to determine whether there exists a confirmation for a particular booking. But why have a separate confirmation model at all? Try just moving the relevant field into the booking model:
class Booking(models.Model):
user = models.ForeignKey(CustomUser, null=True, default='', on_delete=models.CASCADE)
expert = models.ForeignKey(CustomUser, null=True, default='',on_delete=models.CASCADE, related_name='bookings')
title = models.CharField(max_length=200, default='Video call with ..', null=True)
start_time = models.DateTimeField('Start time', null=True)
end_time = models.DateTimeField('End time', null=True)
notes = models.TextField('Notes', blank=True, null=True)
is_confirmed = models.BooleanField(default=False) # just this field since you already have an expert.
That simplifies things, and puts a little less load on your db. Then, you can show only bookings that are confirmed with this template language:
{% if booking.is_confirmed %}
<div class="container" id="booking_content">
<p>{{ booking.title }}</p>
<p>{{ booking.start_time }}</p>
<p>Booking request by: {{ booking.user }}</p>
<p>Expert requested: {{ booking.expert }}</p>
</div>
{% else %}
...
{% endif %}
You may have a good reason for having a separate confirmation model. If so, then this answer is irrelevant. If not, then maybe this could help simplify things for you.

Finding the Cross-Section of two List views in one Django template

I have two models that feed one view.
models.py
class Item(models.Model):
item_name = models.CharField(max_length=100)
item_type = models.ForeignKey(Item_type, on_delete=models.SET_NULL, null=True)
owned_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)****
added_at = models.DateTimeField('date item added')
updated_at = models.DateTimeField('last update')
def __str__(self):
return self.item_name
class Item_status(models.Model):
item = models.ForeignKey(Item, on_delete=models.SET_NULL, null=True)
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
loaned_at = models.DateTimeField(default=None, blank=True, null=True)
due_back = models.DateTimeField(default=None, blank=True, null=True)
def __time__(self):
return self.loaned_at
def itemname(self):
return (self.item.item_name)
I have the following view
views.py
class LoanedItemsByUserListView(LoginRequiredMixin,generic.ListView):
model = Item_status
template_name ='catalog/item_status_list_borrowed_user.html'
paginate_by = 10
def get_queryset(self):
return Item_status.objects.filter(borrower=self.request.user).order_by('due_back')
def get_context_data(self, **kwargs):
context = super(LoanedItemsByUserListView, self).get_context_data(**kwargs)
context['Owned_list'] = Item.objects.filter(owned_by=self.request.user, item_type = 1)
context['Loaned_list'] = Item_status.objects.exclude(borrower=self.request.user).exclude(borrower__isnull=True)
return context
I would like to find the cross section of the 'Owned_list' and the 'Loaned_list' in a single template
Something like
<h2>Loaned Books</h2>
{% if Owned_list %}
<ul>
{% for thing in Owned_list.item_name and in Loned_list.item.item_name %}
<li>
{{thing}}
</li>
{% endfor %}
</ul
{% else %}
<p>There are no books in the library.</p>
{% endif %}
I have take a look at the django documentation here https://docs.djangoproject.com/en/1.11/topics/class-based-views/generic-display/, and around SO but not found exactly what I am looking for.
Thanks!

Categories