Django Prefetch Related Issue - Understand it correctly - python

I do have the following Issue - I want to display all of the bundles with their component relations in a template:
Here is my ORM-Model:
class Component(models.Model):
plenty_var_number = models.CharField(max_length=120, default=None, unique=True, null=True)
plenty_var_id = models.CharField(max_length=120, default=None, unique=True)
description = models.TextField(max_length=1000)
category = models.ForeignKey(Category, on_delete=models.DO_NOTHING, null=False)
updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
def __str__(self):
return f"{self.category.name} - {self.plenty_var_number}"
class Bundle(models.Model):
active = models.BooleanField(default=True)
plenty_var_number = models.CharField(max_length=120, default=None, unique=True, null=True)
plenty_var_id = models.CharField(max_length=120, null=True, default=None)
car = models.ForeignKey(Car, on_delete=models.DO_NOTHING)
# m2m defined by BundleComponentRelation
components = models.ManyToManyField(Component, through="BundleComponentRelation")
linked_to_plenty = models.BooleanField(default=False)
price = models.DecimalField(max_digits=10, decimal_places=2, default=-1.00)
updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
class BundleComponentRelation(models.Model):
component = models.ForeignKey(Component, on_delete=models.DO_NOTHING)
bundle = models.ForeignKey(Bundle, on_delete=models.DO_NOTHING)
qty = models.IntegerField(default=1)
updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
I have played around with select_related and prefetch_related in my view to pass them via context to the template to display it for my users:
html-template:
{% for bundle in bundles %}
<tr>
<td><p class="fw-bold">{{ bundle.plenty_var_number }}</p></td>
<td>{{ bundle.price }}</td>
<td><p class="fw-bolder mb-0">{{ bundle.car }}</p>
<p class="mb-0">{{ bundle.car.roof_type }}</p>
<p class="mb-0">BJ: {{ bundle.car.production_start }}
- {{ bundle.car.production_end }}</p>
</td>
{# Bundle Component Relations here #}
<td style="font-size: 1em;">
<a href={% url "edit_bundle" bundle.pk %}><i
class="fas fa-xl fa-edit "></i></a>
<a href={% url "sync_bundle" bundle.pk %}><i
class="fas fa-xl fa-sync "></i></a>
</td>
</tr>
{% endfor %}
views.py
def bundle_view(request):
bundles = Bundle.objects.prefetch_related('components').all()
print(bundles[0].components)
return render(request, "all_bundles.html", context={"bundles": bundles})
The output of print(bundles[0].components) is bundle.Component.None
I understood the forward usage of select_related but I do have trouble understanding the reverse thinking of the prefetch_related in my situation.
I think my problem is the lookup syntax of prefetch_related, but I might be wrong here.
What am I missing here?
Thanks in advance.
EDIT
I tried:
{% for bundle in bundles %}
<tr>
<td><p class="fw-bold">{{ bundle.plenty_var_number }}</p></td>
<td>{{ bundle.price }}</td>
<td><p class="fw-bolder mb-0">{{ bundle.car }}</p>
<p class="mb-0">{{ bundle.car.roof_type }}</p>
<p class="mb-0">BJ: {{ bundle.car.production_start }}
- {{ bundle.car.production_end }}</p>
</td>
{% for comp_rel in bundle.components.all %}
{{ comp_rel }}
{% endfor %}
<td style="font-size: 1em;">
<a href={% url "edit_bundle" bundle.pk %}><i
class="fas fa-xl fa-edit "></i></a>
<a href={% url "sync_bundle" bundle.pk %}><i
class="fas fa-xl fa-sync "></i></a>
</td>
</tr>
{% endfor %}
I wanted to get only the related components to the currently iterated bundle. The problem I get here is that the template triggers the database again:
monitoring
Simply using the bundle.component in the template led to
ManyRelatedManager object is not iterable TypError

The reason this happens is because the __str__ of the Component accesses the Category, hence for each {{ comp_rel }}, you render, it will make an extra query.
You should use a Prefetch object [Django-doc] to fetch the Categorys in the same query as the one where you fetch the Components:
from app_name.models import Bundle, Component
from django.db.models import Prefetch
def bundle_view(request):
bundles = Bundle.objects.prefetch_related(
Prefetch('components', Component.objects.select_related('category'))
)
return render(request, 'all_bundles.html', {'bundles': bundles})

There is no problem with your prefetch_related, but the way you want to access these objects, you should do bundles[0].components.all() because objects fetched with reverse relation can be accessed same way as M2M fields

Are you sure it must be prefetch_related? I think it must be select_related.
I think you should use Bundle.objects.select_related('components').all() and Component.objects.prefetch_related('bundle_set').all() with your models. But I'm not sure.
And what about template error - you shoud use {% for component in bundle.components.all %} in template.
And there same problem, mb will helpfull.

Related

Django Query with 3 tables

I'm hoping I can get a little guidance.
I'm trying to return data from 3 related tables in my template. In SQL, this is a simple approach, but the Django requirements have me stumbling.
I'd like to display information similar to this:
WaiverAdult.first_name CheckIn.checkintime
WaiverMinor.first_name CheckIn.checkintime
WaiverAdult.first_name CheckIn.checkintime
WaiverMinor.first_name CheckIn.checkintime
WaiverMinor.first_name CheckIn.checkintime
Here are a simplified representation of the models with the relationships defined.
class WaiverAdult(models.Model):
first_name = models.CharField(max_length=50, blank=True)
class WaiverMinor(models.Model):
first_name = models.CharField(max_length=50, blank=True)
parent = models.ForeignKey(WaiverAdult, on_delete=models.CASCADE)
class CheckIns(models.Model):
adult = models.ForeignKey(WaiverParent, on_delete=models.CASCADE, blank=True, null=True)
minor = models.ForeignKey(WaiverChild, on_delete=models.CASCADE, blank=True, null=True)
checkintime = models.DateTimeField(auto_now_add=True)
Here is my simplified view:
class WaiverListView(ListView):
waiver_adults = WaiverAdult.objects.all().prefetch_related(
'waiverminor_set').order_by('created')
queryset = waiver_adults
context_object_name = "waiver_list"
template_name = 'waiver/waiver_list.html'
And, this is my template.
{% for adult in waiver_list %}
<tr>
<td>{{adult.first_name}}</td>
<td>insert the adult checkin time here</td>
</tr>
{% for child in adult.waiverminor_set.all %}
<tr>
<td>{{child.first_name}}</td>
<td>insert the child checkin time here</td>
</tr>
{% endfor %}
{% endfor %}
I would be very appreciative of details in the explanations as I really want to understand how this all works.
Firstly, for every Foreign key you are creating I suggest you to add a related_name this way you specify the name of reverse relation ship between the children model and parent model in your case for example, your code should be:
class WaiverAdult(models.Model):
first_name = models.CharField(max_length=50, blank=True)
class WaiverMinor(models.Model):
first_name = models.CharField(max_length=50, blank=True)
parent = models.ForeignKey(WaiverAdult, on_delete=models.CASCADE,related_name='minor_of_adult')
and let's explain how does it work, you want to know and get all the minors of some adult, what you should do is specify the adult, and get all minor related to that adult, in code context (try it in django shell, using python manage.py shell):
adult = WaiverAdult.objects.get(first_name='cbirch') #specifying the adult
adult.minor_of_adult.all() #notice how we access the minor using the related_name
same thing apply to the last model CheckIns.
With the models you have created it is possible to have multiple checkin times per parent and child so you need to loop through the list. Also your foreign keys refer to WaiverParent and WaiverChild whilst the actual model names are WaiverAdult and WaiverMinor. You could try the following template:
{% for adult in waiver_list %}
<tr>
<td>{{adult.first_name}}</td>
<td>
{% for checkin in adult.checkins_set.all %}
{{ checkin.checkintime }}
{% endfor %}
</td>
</tr>
{% for child in adult.waiverminor_set.all %}
<tr>
<td>{{child.first_name}}</td>
<td>
{% for checkin in child.parent.checkins_set.all %}
{{ checkin.checkintime }}
{% endfor %}
</td>
</tr>
{% endfor %}
{% endfor %}

Custom User PK (Primary Key) - Django - Python

I built a website with Python Django and part of it displays lines of already input data with the primary key next to it. Now I have separate user data by using a foreign key field in each one of my models and the specific user's data is only shown to that logged in user except that all the data regardless of the user is saved in one spot with the model shown in the admin interface(because I'm using foreign keys to separate data). My problem is that I need to display the primary key of just the logged-in user. Take this, for example, if User1 adds 1 line of data to their page then User2 adds 1 line of data on their page which will appear separate from one another, then User1 uploads another line of data, Then I user traditional primary key to number the data lines of User1 the numbers will be 1 and three instead of keeping them in numerical order of 1 and 2 and disregarding User2's data in the counting of the data. It's as if I need a separate primary key for each user. Sorry this is really hard to explain. I have found a temporary solution of using {{ forloop.revcounter }} to count in a for loop instead of using the primary key but the problem with this is that when data is deleted all the numbers above it go down one because this tag just counts how many times the for loop has looped. I have found no information of the internet about this and how to solve it beyond this. I might just be looking in the wrong places but I need help. Thanks!
EDIT:
models.py
class Sheet_Extinguisher(models.Model):
user = models.ForeignKey(User, default=True, related_name="Extinguisher", on_delete=models.PROTECT)
floor = models.CharField(max_length=5, choices=FLOOR_LEVEL_FE, blank=True, null=True, verbose_name='Floor / Level')
area_room = models.CharField(max_length=35, blank=True, null=True, verbose_name='Area / Room')
building_address = models.CharField(max_length=46, choices=BUILDING_ADDRESS, blank=True, null=True, verbose_name='Building and Address')
type = MultiSelectField(choices=TYPE_FE, blank=True, null=True, verbose_name='Type')
size = models.CharField(max_length=3, choices=SIZE_FE, blank=True, null=True, verbose_name='Size')
hydrostatic_date = models.DateField(blank=True, null=True, verbose_name='Last Hydrostatic Test')
mass_notes = models.CharField(max_length=20, blank=True, null=True, verbose_name='Mass Fire Tech Notes')
notes = models.TextField(max_length=500, blank=True, null=True, verbose_name="Inspector's note")
class Meta:
ordering = ['building_address']
def __str__(self):
return self.building_address or 'None'
def get_absolute_url(self):
return reverse('list_extinguisher')
HTML (with pk):
{% if filter.qs %}
{% for post in filter.qs %}
<tr>
<td><div class="item1" style="text-align: center;">{{ post.pk }}</div></td>
{% endfor %}
{% endif %}
HTML (just with forloop counter):
{% if filter.qs %}
{% for post in filter.qs %}
<tr>
<td><div class="item1" style="text-align: center;">{{ forloop.revcounter }}</div></td>
{% endfor %}
{% endif %}

Include Object and Object's Foreign Keys in One Query Django

I have the following models:
class Document(models.Model):
name = models.CharField(max_length=30, null=True, blank=True)
reference = models.CharField(max_length=30, null=True, blank=True)
def __str__(self):
return self.Name
class Subdocument(models.Model)
document = models.ForeignKey(Document, null=True, default=None)
pdf = models.FileField(upload_to='media')
Ultimately I want to show both the name from Document and the pdf from Subdocument in the same <li> in my template within a for loop. So it could be something like:
{% for item in something %}
<li class="list-group-item">
{{ item.Name }} {{ item.pdf }}
</li>
{% endfor %}
My issue is because they are in separate models, I'm not sure how to get fields from both. So what query, or queries could I run in my view in order to make this possible? Any other approach is welcome, this is just to illustrate my end goal.
Thank you!
EDIT:
views.py
def doc_view(request):
something = Subdocument.objects.filter(document__isnull=False)
return render(request, 'my_template.html', context={'something': something})
Have you tried something like this,
# views.py
def my_view(request):
docs = Subdocument.objects.filter(document__isnull=False) # filtering out empty documents
return render(request, 'my_template.html', context={'docs': docs})
# my_template.html
{% for doc in docs %}
<li class="list-group-item">
{{ doc.document.name }} {{ doc.pdf.url }}
</li>
{% endfor %}
Try this
models.py
replace
name = models.CharField(max_length=30, null=True, blank=True)
to
name = models.CharField(max_length=30, null=True, blank=False)
template
{% for item in something %}
<li class="list-group-item">
{{ item.document.name }} {{ item.pdf }}
</li>
{% endfor %}

How to capture the number of downloads of a specific file

I have been working on a project that involves downloading files. I have been tasked to create a report of how many downloads per file. Here is my code:
reports.py
def dataset_download(request):
download = DataSet.objects.annotate(numdownload=Count('name'),)
return render(request, "my_admin/dataset_download.html", {"download":download})
my models.py
class DataSet(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
purpose = models.TextField()
temporal_scope = models.TextField()
taxonomic_scope = models.TextField()
my urls.py
path('dataset_download/', reports.dataset_download, name='dataset_download'),
and finally my html
{% for d in download %}
{% if d.name != "" %}
<tr>
<td>{{ d.name }}</td>
<td>{{ d.numdownload }}</td>
<td>
<a href="/dataset/?name={{ d.name }}" class="btn btn-primary btn-xs">
<i class="fa fa-eye"></i>
View Details
</a>
</td>
</tr>
{% endif %}
{% endfor %}
You can make use of the following Django app to keep track of all of your downloads.
You can go through the documentation here.
https://pypi.org/project/django-download-stats/
You can create a new attribute and method to your model:
class DataSet(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
purpose = models.TextField()
temporal_scope = models.TextField()
taxonomic_scope = models.TextField()
numdownload = models.IntegerField(default=0)
def increment_numdownload(self):
self.numdownload +=1
return self.numdownload
And call this method whenever download event is loaded.

What should be the correct approach to pass in primary key into URL?

Right now I am using Class-based delete view, and my URL contains two arguments which are the primary keys of my 2 models: Post and Lesson. However, I am encountering an Attribute Error: Generic detail view LessonDeleteView must be called with either an object pk or a slug in the URLconf.
These are my two models Lesson and Post:
class Post(models.Model):
title = models.CharField(max_length=100)
image = models.ImageField(default = 'default0.jpg', upload_to='course_image/')
description = models.TextField()
price = models.DecimalField(decimal_places=2, max_digits=6)
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
rating = models.IntegerField(default = 0)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk' : self.pk})
class Lesson(models.Model):
title = models.CharField(max_length=100)
file = models.FileField(upload_to="lesson/pdf")
date_posted = models.DateTimeField(default=timezone.now)
post = models.ForeignKey(Post, on_delete=models.CASCADE, null=False, blank=False)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('lesson_upload', kwargs={'pk': self.pk})
This is my URLs.py:
path('post/<int:post_id>/lesson_uploaded/<int:lesson_id>', LessonDeleteView.as_view(), name='lesson_delete'),
Right now this is how I am trying to insert the parameters in my html template:
{% block content %}
<div id="main">
<table class="table mb-0">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Download</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for l in Lesson %}
<tr>
<td>
{% if l.file %}
{{ l.title }}
{% else %}
<h6>Not available</h6>
{% endif %}
</td>
<td>{{ l.post.author }}</td>
<td>{% if l.file %}
Download
{% else %}
<h6>Not available</h6>
{% endif %}
</td>
<td> <a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'lesson_delete' post.id l.id %}">Delete</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
This is my class-based view:
class LessonDeleteView(DeleteView):
model = Lesson
success_url = '../'
template_name = 'lesson_confirm_delete.html'
As you are deleting Lesson, you don't need to provide Post ID. You can simply use Lesson ID here. So try like this:
# url
path('post/lesson_uploaded/<int:pk>/', LessonDeleteView.as_view(), name='lesson_delete'), # using pk instead of lession_id, it will resolve the error you are facing
# template
<td> <a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'lesson_delete' l.id %}">Delete</a>
</td>
Update
from comment section, you can override the delete method like this:
class LessonDeleteView(DeleteView):
model = Lesson
success_url = '../'
template_name = 'lesson_confirm_delete.html'
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
self.object.file.delete() # <-- added file delete code
success_url = self.get_success_url()
self.object.delete()
return HttpResponseRedirect(success_url)
Why are you passing the lesson id.
Since Post gets deleted by default lesson would get deleted as well. Check this video to better understand how to pass the id and details on Delete View.
DeleteView(Class Based Views)

Categories

Resources