Query two models in Django into one queryset - python

I have two models in my django app:
class Person(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
persons = models.ManyToManyField(Person, related_name="books")
Now I need to create a view that will make one query for regex to both of the models, find the ones that are matching and display them in template.
If I do:
class SearchListView(ListView):
queryset = Person.objects.filter(name__icontains="a")
book_queryset = Book.objects.filter(title__icontains="a")
I get an error that ListView accepts only one queryset.
What is the typical solution to such problem?

You need to do something a little bit different here:
class SearchListView(ListView):
queryset = Person.objects.filter(name__icontains="a")
def get_context_data(self, **kwargs):
context = super(SearchListView, self).get_context_data(**kwargs)
context['book_queryset'] = Book.objects.filter(title__icontains="a")
return context
Then in your view you can do somenting like the following:
{% for object in object_list %}
<p>{{object}}</p>
{% endfor %}
{% for object in book_queryset %}
<p>{{object}}</p>
{% endfor %}
The reason why the way you are using is not working is because ListView inherit from MultipleObjectMixin the queryset property and that property is passed to object_list context variable in the template, that happens under the hood and if you want to pass more context variables to the template you need to follow the approach I shared.

Related

Django filtering relation in ListView

Given the models
class TaskGroup(models.Model):
name = models.CharField(max_length=256)
class Task(models.Model):
name = models.CharField(max_length=256)
group = models.ForeignKey(TaskGroup, on_delete=models.CASCADE)
completed = models.BooleanField(default=False)
completed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
and the list view
class TaskGroupListView(ListView):
model = TaskGroup
I'd like to display the list of task groups with corresponding tasks. The catch is - I only want to show the tasks that have not been completed or have been completed by the user, or if the user as the attribute user.type == "ADMIN" set show all groups and all tasks.
Right now I have a template that looks like:
{% for taskgroup in object_list %}
<h1>{{ taskgroup.name }}</h1>
<ul>
{% for task in taskgroup.task_set.all %}
<li>{{ task.name }}</li>
{% endfor %}
</ul>
{% endfor %}
I know that I can modify the queryset of a list view by overriding get_queryset like:
def get_queryset(self):
if self.request.user == "ADMIN":
return TaskGroup.objects.all()
else:
...
but I'm not sure how to go about filtering the Task relations on the TaskGroups in the else clause.
I've thought about creating a manager subclass for Task that can filter based on .completed and .completed_by that I can use in the template but that seems against the philosophy of Django - I'd like to keep all the logic in the view (this may be way off the mark so please correct me, its been a while since I've touched django/read two scoops of django).
Is there some idiomatic way to do this? Should I discard ListView entirely and write some custom logic? any guidance here is helpful. Thanks.
You can use prefetch_related with a Prefetch that uses a custom filtered queryset like this:
from django.db.models import Prefetch, Q
def get_queryset(self):
if self.request.user.is_admin:
return TaskGroup.objects.all()
return TaskGroup.objects.prefetch_related(
Prefetch(
'task_set',
queryset=Task.objects.filter(Q(completed=False) | Q(completed_by=self.request.user))
)
)
This will get all the TaskGroups with the related Taskss (in task_set) filtered by those that are not yet completed or are completed by the current user.

I would like to manipulate the data from the views to / render (html) in Django

I have this view using generic in django and I need to manipulate it. But it seems that whenever I tried using setattr it doesn't manipulate/replace the data in the queryset.
view.py
class ArticleListView(ListView): #using the Generic ListView in django
template_name = 'article/article_list_view.html'
queryset = Article.objects.all()[:5]
def get(self, request, *args, **kwargs):
setattr(self.queryset[0], 'title', 'something') #Trying to change the title in the first element
print(getattr(self.queryset[0], 'title')) #it outputs the old value
return super().get(request, *args, **kwargs)
article_list_view.html
{% extends 'base.html' %}
{% block content %}
{% for instance in object_list %}
<h5>{{instance.title}}</h5>
{% autoescape off %}
<p>{{instance.content| capfirst}}</p>
{% endautoescape %}
{% endfor %}
{% endblock content %}
Do you have any idea to solve this issue? Or maybe because it was immutable?
This is a challenge because according to Django's docs on querysets "a QuerySet can be constructed, filtered, sliced, and generally passed around without actually hitting the database". The first thing you and I both tried was grabbing the first object out of the queryset and setting the attribute. This doesn't help because the queryset itself isn't being evaluated until you iterate over it: {% for instance in object_list %}. When that happens the model instance will be retrieved from the database again.
The best solution I can see is to force the queryset to be evaluated before updating the model instance.
class ArticleListView(ListView):
template_name = 'article/article_list_view.html'
queryset = Article.objects.all()[:5]
def get_context_data(self, *, object_list=None, **kwargs):
# Overwriting this method to receive exactly the same arguments as the original
# https://github.com/django/django/blob/3.0.6/django/views/generic/list.py#L113
if object_list is None:
object_list = self.object_list
if not isinstance(object_list, tuple): # Force the queryset into a tuple so it's evaluated
object_list = tuple(object_list)
object_list[0].title = 'something' # Update the instance's title
return super().get_context_data(object_list=object_list, **kwargs)
This does have the downside of article_list won't be available in the context unless you explicitly set context_object_name = 'article_list' on your view, but you already weren't using that variable.
I've tested out parts of this code to verify that my theory is right, but I haven't run the code in it's entirety, so you might need to tweak things to get it working exactly as you want it.

Django: How to iterate over two one-two-many table relationships?

I am trying to build a simple web page that queries three tables. There is a Company table that has a one-to-many relationship with a Position table, as well as a one-to-many relationship with a Project table.
The goal is to have the page display a given company once, along with all positions and and projects associated with said company. Then, move on to display the next company, any positions held there and projects completed.
Below is the closest I've come to getting this right. But, the obvious problem is that if there is more than one project associated with a given company, you'll see that company listed more than once.
I'm new to Django, so in the interest of learning, I wanted to beat my own head sufficiently hard before asking for help; but I could really use some fresh ideas at this point.
Also: I can see how a nested for loop might work here, but I'm just not clear on how the mechanics of that would work with the query, and then within the template.
Models:
from django.db import models
class Company(models.Model):
company_id = models.AutoField(primary_key=True)
company_name = models.CharField(max_length=20)
company_logo = models.ImageField(upload_to='images/')
def __str__(self):
return self.company_name
class Position(models.Model):
position_id = models.AutoField(primary_key=True)
position_title = models.CharField(max_length=55)
company_id = models.ForeignKey('professional.Company',
on_delete=models.CASCADE,
blank=True,
null=True)
begin_date = models.DateField()
end_date = models.DateField()
def __str__(self):
return self.position_title
class Project(models.Model):
project_id = models.AutoField(primary_key=True)
project_name = models.CharField(max_length=55)
company_id = models.ForeignKey('professional.Company',
on_delete=models.CASCADE,
blank=True,
null=True)
project_description = models.CharField(max_length=500)
project_image = models.ImageField(upload_to='images/')
def __str__(self):
return self.project_name
View:
from django.views.generic import TemplateView, ListView
from professional.models import Company
class ProfessionalHome(TemplateView):
template_name = 'professional/professional_home.html'
class TechnologyListView(ListView):
template_name = 'professional/__technology.html'
context_object_name = 'technology_list'
def get_queryset(self):
return Company.objects.values('company_name','position__position_title', 'project__project_name')
HTML and template:
{% for job in technology_list %}
<h1>{{job.company_name}}</h1>
<h1>Position: {{job.position__position_title}}</h1>
<h1>project: {{job.project__project_name}}</h1>
{% endfor %}
Instead of values in get_queryset method, you can return the actual queryset and then iterate over it to build your view.
def get_queryset(self):
return Company.objects.all()
Then in your template:
{% for job in technology_list %}
<h1>{{job.company_name}}</h1>
{% for position in job.position_set.all() %}
<h1>Position: {{position.position_title}}</h1>
{% endfor %}
{% for project in job.position_set.all() %}
<h1>project: {{project.project_name}}</h1>
{% endfor %}
{% endfor %}
If you want to iterate over companies, then you should use the Company model as the basis for your view, not Technology. Also, you should avoid values and values_list unless you know you have a good reason, which you don't here. You can use prefetch_related() to reduce the number of reverse queries. So:
class TechnologyListView(ListView):
def get_queryset(self):
return Company.objects.all.prefetch_related('project','position')
...
{% for company in company_list %}
<h1>{{company.company_name}}</h1>
{% for position in company.position_set.all %}
<h1>Position: {{ position.position_title }}</h1>
{% endfor %}
{% for project in company.project_set.all %}
<h1>project: {{ project.project_name }}</h1>
{% endfor %}
{% endfor %}
(Note, you should avoid giving your ForeignKey fields names ending in "_id". The Django field refers to the entire Company, not the ID; the fields should be called just company. The underlying database will be suffixed with _id anyway. Also, you don't need to use model_name prefixes on all your fields; it will be obvious from the object they are accessed on.)

Django get abstract model value in template

I have abstract models and it displays when item created. How can i get this variable and display in my template?
class BaseModel(models.Model):
class Meta:
abstract = True
_created = models.DateTimeField(auto_now_add=True)
class Toy(BaseModel):
name = models.CharField(max_length=250)
views.py
toys = Toy.objects.all()
index.html
{% for k in toys %}
{{k.created}}
{% endfor %}
I tried this one but it doesn't work.
It's not ideal, but if you only need the _created field, create an empty list in your view, and then populate it with the field for each instance. Then, return the new list as a context variable.
For example,
created_values = []
for value in Toy.objects.all():
created_values.append(value._created)
The above is not tested, but I believe it should work.

Django Detailview display properties of parent

I'm trying to use DetailView. I don't have a deep understanding of it.
What I'm trying to do is display the properties of an object from the pk. That is, I'm at, say, /notendur/34, and I want to display information about the object with pk=34.
I'm trying to make sense of this:
https://docs.djangoproject.com/en/dev/intro/tutorial04/
But I can't make sense of it. Perhaps one of you can help me understand? I'm looking at the second block of code in that link, not the first one.
{% extends "index.html" %}
{% block content %}
{{ "placeholder" }}
{% endfor %}
{% endblock %}
I'm looking to use the HTML to fetch the pk from /notendur/34 for example.
The detail view automatically pass object with primary key 34 named as object in context. You can access that in template e.g. {{ object.pk }} or {{ object.some_property_name }}
First of all in your views you need to load the appropriate class:
from django.views.generic import (
DetailView,
)
According to the Class Based View Inspector (keep a ref on this link):
http://ccbv.co.uk/
The DetailView has the following properties:
content_type = None
context_object_name = None
http_method_names = [u'get', u'post', u'put', u'patch', u'delete', u'head', u'options', u'trace'] View
model = None
pk_url_kwarg = 'pk'
queryset = None
response_class = <class 'django.template.response.TemplateResponse'>
slug_field = 'slug'
slug_url_kwarg = 'slug'
template_name = None
template_name_field = None
template_name_suffix = '_detail'
As you can see from the above, when the DetailView is called, it will check first for the existence of a pk or slug argument in the request,
this is done in your urls.py file:
urlpatterns = patterns('',
...
url(r'^view/(?P<slug>[\d]+)/$', MyTestDetailView.as_view(), name='myurl-name'),
...
)
By defining the slug parameter in the url, the DetailView knows which item you request details for (alternative you could use pk, but slug makes more friendly urls).
It then will fetch the model or the queryset (defined in your view) based on either the slug or pk field, this is performed in the def get_object(self, queryset=None) method.
After grabbing the model (if it fails it raises a 404 error) you can use the object within your template (specified under the template_name property) as:
{{ object }}
If you want to change the name of the template variable, you can assign a context_object_name property. A quick example is bellow:
from django.views.generic import (
DetailView,
)
from myapp.models import (
MyModel,
)
class MyTestDetailView(DetailView):
"""
Set context object name to mytemplatevar
"""
context_object_name = "mytemplatevar"
"""
Define the model to use
"""
model = MyModel
"""
Define the template
"""
template_name = "myapp/detail_view.html"
Appart from that you don't need anything else, in your template then you can access your object:
{{ mytemplatevar.something }}

Categories