How to create Views with less queries in django - python

Models.py
class Book(models.Model):
created_at = models.DateTimeField(auto_now_add=True, editable=False)
name = models.CharField(max_length=255, unique=True)
slug = models.SlugField(max_length=255, blank=True, default='', unique=True)
class BookTranslation(models.Model):
book = models.ForeignKey(Book, related_name='book_translations')
language = models.CharField(max_length=2, choices=LANGUAGE_CHOICES)
class Chapter(models.Model):
chapter = models.IntegerField()
book_translation = models.ForeignKey(BookTranslation, related_name='related_chapters')
class Page(models.Model):
chapter = models.ForeignKey(Chapter, related_name='related_pages')
page = models.IntegerField()
url = models.CharField(max_length=255)
urls.py
url(r'^(?P<language>[\w-]{2})-book/(?P<slug>[\w-]+)/(?P<chapter>\d+)/(?P<page>\d+)/$",
PageView(),
name="page")
I tried different approaches to make PageView() and ended up with this:
class PageView(DetailView):
model = Book
template_name = 'bookinfo/book_page.html'
context_object_name = 'book'
def get_context_data(self, **kwargs):
context = super(PageView, self).get_context_data(**kwargs)
context['book_translation'] = BookTranslation.objects.get(
book=context['book'], language=self.kwargs['language'])
context['chapter'] = Chapter.objects.get(
book_translation=context['book_translation'].id, chapter=self.kwargs['chapter'])
context['page'] = Page.objects.get(
chapter=context['chapter'].id, page=self.kwargs['page'])
context['chapter_list'] = Chapter.objects.filter(
book_translation=context['book_translation'].id)
context['page_list'] = Page.objects.filter(
chapter=context['chapter'].id)
return context
But this way, every time i open this page, i make 6 requests to the DB, when i could easily get the arguments in context['book'], context['book_translation'] context['chapter'] and context['page'] with a singole SQL query like this:
SELECT *
FROM Book b, BookTranslation bt, Chapter c, Page p
WHERE b.slug=self.kwargs['slug'] and
b.name=bt.book_id and
bt.language=self.kwargs['language'] and
c.book_translation_id=bt.id and
c.chapter=self.kwargs['chapter'] and
c.id=p.chapter_id and
p.page=self.kwargs['page']
Can someone explain me how can i make this View(preferably class based) more performance? I probably didn't understand how they work...

If you want to retrieve related foreign keys use select_related by overriding get_queryset of SingleObjectMixin as follows:
class PageView(DetailView):
...
def get_queryset(self, *args, **kwargs):
qs = super(PageView, self).get_queryset(*args, **kwargs)
return qs.select_related()
This will fetch all related objects in a single query, so as long as the queryset is not evaluated, all the related objects in your view's get_context_data should not be queried again.
Refer: https://docs.djangoproject.com/en/1.6/ref/models/querysets/#select-related

Related

Django REST framework - filtering against query param with date Outside Views.py file

I created my "API" using REST framework, now trying to do filtering for it. That's how my models.py look for BookingStatement model.
class BookingStatement(BaseModel):
ticket_number = models.PositiveIntegerField(unique=True)
booking = models.OneToOneField(Booking, on_delete=models.PROTECT)
user_rate = AmountField()
agent_rate = AmountField()
total_amount = AmountField()
class Meta:
default_permissions = ()
def __str__(self):
return str(self.id)
Booking is One to One Key so the booking model has following Attributes.
class Booking(BaseModel):
bus_seat = models.ManyToManyField(Seat)
schedule = models.ForeignKey(Schedule, on_delete=models.PROTECT)
boarding_point = models.ForeignKey(
BoardingPoint,
on_delete=models.PROTECT,
null=True
)
remarks = models.JSONField(null=True, blank=True)
contact = PhoneNumberField(null=True)
booking_date_time = models.DateTimeField(auto_now_add=True)
class Meta:
default_permissions = ()
verbose_name = 'Booking'
verbose_name_plural = 'Bookings'
def __str__(self):
return '{}-{}'.format(self.user, self.customer_name)
I used generic ListAPIView in my views.py as following.
class BusCompanyTicketDetailView(generics.ListAPIView, BusCompanyMixin):
serializer_class = serializers.TicketDetailResponseSerializer
def get_queryset(self):
travel_date = (int(self.request.query_params.get('booking_date')))
print(booking_date)
return usecases.ListBusCompanyTicketUseCase(date=#'what should i pass?'#).execute()
I use usecases.py to filter booking_date_time with url as following.
http://127.0.0.1:8000/api/v1/ticket/list?booking_date=2021-1-29
So my usecase file to filter the Booking time is as following.
class ListBusCompanyTicketUseCase(BaseUseCase):
def __init__(self, date:datetime):
self._date = datetime
def execute(self):
self._factory()
return self._booking_statements
def _factory(self):
self._booking_statements = BookingStatement.objects.filter(booking__booking_date_time=?? need to get date from views.)
Problem is I don't know to how to get query params from url in my usecases to filter with booking date any help will be very helpful.
you should use django-filter to implement filtering on the viewsets. It's a bit of knowledge you have to build up, but once you understand it, you can do a lot of complex filtering logic with it. Trying to implement a filtering system yourself is always more difficult in the long run. For starting point, check out the official documentation: https://www.django-rest-framework.org/api-guide/filtering/. For the filter, check out the documentation on DRF integration: https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html.

Django Forms: How to make form.fields by Model.QuerySet in dynamic?

I need to generate Django forms.Form object with fields not from Model.fields (Database Table Columns names), but by records in Model.Table.
I have table Model in models.py:
class MntClasses(models.Model):
type = models.CharField(max_length=2, blank=True, null=True)
class_subtype = models.CharField(max_length=45, blank=True, null=True)
text = models.CharField(max_length=45, blank=True, null=True)
explanation = models.CharField(max_length=45, blank=True, null=True)
name = models.CharField(max_length=45, blank=True, null=True)
views.py
# Form generate
class Form_classes(forms.Form):
def __int__(self, *args, **kwargs,):
super(Form_classes, self).__init__(*args, **kwargs)
print("some")
for fld_ in args:
self.fields[fld_.name] = forms.BooleanField(label=fld_.text)
#Main
def page_Category_Main(request, post):
db_table = MntClasses
form_fld = db_table.objects.all()
'''
This QuerySet 20 records returned of <MntClasses: MntClasses object (0-19)> type.
QuerySet Filds Names: 'name','type','expalnation', 'text'
''':
form_ = Form_classes(*form_fld)
exit_ = {
'form': form_,
}
return render(request, template_name="category.html", context=exit_)
It raise TypeError
init() takes from 1 to 12 positional arguments but 20 were given
So, i have no idea what does it mean this code taken from were: Auto-generate form fields for a Form in django:
def __int__(self, *args, **kwargs,):
super(Form_classes, self).__init__(*args, **kwargs)
What is this "*args", how to use it?
How can I generate Form.fields by QuerySet form_fld.name in that case?
About args
To understand what args is you can take a look at this post which will eventually direct you to https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists.
Basically it's a special python syntax which allows a function to retrieve multiple arguments as a tuple in a single variable.
About Django's Models
Do you really want to generate multiple forms?
If this is the case you would need to loop over your table:
forms = []
db_table = MntClasses
for item in db_table.objects.all():
forms.append(Form_classes(item))
exit_ = {
'form': forms,
}
the trick is then how you deal with the multiple forms on the front end.
Also you probably want to switch to a ModelForm which would look something like:
from django import forms
from myapp.models import MntClasses
class FormMnt(forms.ModelForm):
class Meta:
model = MntClasses
...
In case you want to handle multiple instances of MntClasses in a single form, you should look at Django's formsets.

DRF-Filtering with multiple query params and foreign keys

I'm new to Django and I'm trying to create a route that can be called to retrieve an array of vehicles from my database, but I want the user to be able to provide multiple query params in the url (something like: http://127.0.0.1:8000/vehicles/?year=2020&make=Toyota). The problem that I have come across is that my vehicle model includes references to foreign keys for the make and the v_model (so named to avoid conflict with the Django "model"). I have a solution that doesn't seem very graceful. The fact that I have three nested conditional statements for each search field makes me suspicious. I tried using "filters.SearchFilter" but I was only able to provide a single value on which to base the search. So a request like this: http://127.0.0.1:8000/vehicles/?search=2020&search=Toyota would only search for vehicles with a make of "Toyota", ignoring the "year" parameter.
Is there some other way to do this that is cleaner or more "Django-approved"?
Here is my code:
models.py:
class Make(models.Model):
name = models.CharField(max_length=200, unique=True)
def __str__(self):
return self.name
class VModel(models.Model):
name = models.CharField(max_length=200, unique=True)
make = models.ForeignKey(Make, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Vehicle(models.Model):
make = models.ForeignKey(Make, on_delete=models.CASCADE)
v_model = models.ForeignKey(VModel, on_delete=models.CASCADE)
year = models.CharField(max_length=5)
def __str__(self):
return self.year + " " + self.v_model.name
views.py:
Here is my attempt with filters.SearchFilter:
queryset = Vehicle.objects.all()
serializer_class = VehicleSerializer
filter_backends = [filters.SearchFilter]
search_fields = ['year', 'v_model__name','make__name']
And here is my "working" solution that seems hacky:
(NOTE: I am using name__icontains so that if a user enters "Toyot" it will still get all cars with a make of Toyota).
class VehicleListViewSet(viewsets.ModelViewSet):
serializer_class = VehicleSerializer
queryset = Vehicle.objects.all()
pagination_class = CustomPagination
def get_queryset(self):
qs = super().get_queryset()
selected_make = self.request.query_params.get('make', None)
if selected_make:
try:
found_make = Make.objects.get(name__icontains=selected_make)
except:
return []
if found_make:
if found_make.id:
qs = qs.filter(make=found_make.id)
selected_v_model = self.request.query_params.get('v_model', None)
if selected_v_model:
try:
found_v_model = VModel.objects.get(name__icontains=selected_v_model)
except:
return []
if found_v_model:
if found_v_model.id:
qs = qs.filter(v_model=found_v_model.id)
selected_year = self.request.query_params.get('year', None)
if selected_year:
qs = qs.filter(year=selected_year)
return qs
You shouldn't be using filters.SearchFilter. Instead, use a filterset_fields attribute in your ViewSet, like the example from this section of the documentation.
Your viewset would be like this:
class VehicleListViewSet(viewsets.ModelViewSet):
serializer_class = VehicleSerializer
queryset = Vehicle.objects.all()
pagination_class = CustomPagination
filter_backends = [DjangoFilterBackend]
filterset_fields = ['year', 'v_model__name','make__name']
(Note there's no get_queryset override) You'll be able to query your API like this:
http://127.0.0.1:8000/vehicles/?year=2020&make__name=Toyota&v_model__name=Corolla

Django QuerySet Any

I want to get a queryset of all books that are currently in Library (the dateReturn of a currently rent is set to null).
I'm new to python and i don't know how to do subqueries in django.
In other words in want to filter every related object field on a condition, if only one related-object doesn't match to this condition the object must not be returned
models.py
class Book(models.Model):
cod = models.CharField(max_length=255, unique=True)
title = models.CharField(max_length=255)
.....
class Rent(models.Model):
dateRent = models.DateField(default=timezone.now)
dateReturn = models.DateField(null=True, blank=True)
book = models.ForeignKey(modelsBook.Book, on_delete=models.DO_NOTHING, related_name="rent")
.....
P.S:
I need this subquery for display book currently not render in a choiceField
forms.py
class RentForm(forms.ModelForm):
__pk=None
def __init__(self, *args, **kwargs):
self.__pk = kwargs.pop('pk', None)
super(RentForm, self).__init__(*args, **kwargs)
class Meta():
model = models.Rent
fields = ('book', 'student')
labels = {
'book' : _('Libro'),
'student' : _('Studente'),
}
widgets = {
'book': queryset,
.....
You can filter objects through the related_name.
class RentForm(forms.ModelForm):
__pk=None
def __init__(self, *args, **kwargs):
self.__pk = kwargs.pop('pk', None)
super(RentForm, self).__init__(*args, **kwargs)
self.fields['book'].queryset = Book.objects.exclude(rent__dateReturn__isnull=True)
...

django one template - many views - beginner

I have managed (with a lot of assistance) to create a basic test blog in django 1.4.6 and python 2.7.
I can display the blog entries as a list (main page) and then allow the user to view each individual blog entry by clicking on the blog entry link.
I am now attempting to display the blog entries by author and by archive (by the published date of the blog entry).
Is it possible to have the one blog page that can list the blogs by date (most recent first), by author and by archive (by the date - most recent last)?
I am assuming the code is written in the urls.py and views.py files with the output to the single blog page, but I haven't yet found an example to work from and I can't quite figure this out on my own.
Here is my models.py:
class BlogPostDetails(models.Model, FillableModelWithLanguageVersion):
blog_post_title = models.CharField(max_length=50)
blog_post_date_published = models.DateField()
blog_post_author = models.CharField(max_length=25)
blog_post_body = models.TextField()
blog_post_allow_comments = models.BooleanField(default=False)
blog_post_timestamp_added = models.DateTimeField(auto_now_add=True, auto_now=False)
blog_post_timestamp_updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __unicode__(self):
return self.blog_post_title
class Meta:
ordering = ['-blog_post_date_published']
verbose_name = ('Blog')
verbose_name_plural = ('Blogs')
Here is my views.py:
def blog_post_item(request, blog_posts_id):
blog_posts = BlogPostDetails.objects.get(pk=blog_posts_id)
language_versions = get_language_versions(user=request.user)
return render(request, 'core/details/blog/blog_item.html', {
'blog_posts': blog_posts,
'display_default_language': display_default_language(request.user),
'languages': LANGUAGES,
'language_versions': language_versions,
'language_versions_num': len(language_versions),
})
def blog_post_list(request):
blog_posts = BlogPostDetails.objects.filter(blog_post_date_published__lt=datetime.today())
language_versions = get_language_versions(user=request.user)
return render(request, 'core/details/blog/blog_list.html', {
'blog_posts': blog_posts,
'display_default_language': display_default_language(request.user),
'languages': LANGUAGES,
'language_versions': language_versions,
'language_versions_num': len(language_versions),
})
Here is my urls.py
url(r'^details/blog/blog_list/$', 'blog_post_list', name='blog_post_list'),
url(r'^details/blog/blog_item/(?P<blog_posts_id>\d+)/$', 'blog_post_item', name='blog_post_item'),
Some advices:
You shouldn't use model name prefix in model field names, as you see code look much better:
class BlogPost(models.Model, FillableModelWithLanguageVersion):
title = models.CharField(max_length=50)
date_published = models.DateField()
author = models.CharField(max_length=25)
body = models.TextField()
allow_comments = models.BooleanField(default=False)
timestamp_added = models.DateTimeField(auto_now_add=True, auto_now=False)
timestamp_updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __unicode__(self):
return self.title
class Meta:
ordering = ['-date_published']
verbose_name = ('Blog')
verbose_name_plural = ('Blogs')
Update django up to 1.6
User class-based views(filtering and ordering example)
And yes - it is posible to use one template for you task
You should pass order type as url parameter.
url(r'^details/blog/blog_list/(?P<order_type>\w+)/$', 'blog_post_list', name='blog_post_list'),
def blog_post_list(request, order_type='date_published'):
blog_posts = BlogPost.objects.filter(date_published__lt=datetime.today()).order_by(order_type)
language_versions = get_language_versions(user=request.user)
return render(request, 'core/details/blog/blog_list.html', {
'blog_posts': blog_posts,
'display_default_language': display_default_language(request.user),
'languages': LANGUAGES,
'language_versions': language_versions,
'language_versions_num': len(language_versions),
})
If you want to use one url - the only way that i see is javascript on page ordering.

Categories