Django: Add result from second queryset to first queryset - python

I have two models from two different databases (one read-only) without ForeignKey between the two models (did not get that working, as far i found it isn't possible).
In the main model I store the ID from the second model (read-only DB).
I want to display multiple records/rows on one view (like al table)
There for I want to get the content of the second model with the id from the main model.
and combine it to one row.
Normal you can get it by the ForeignKey but did won't work with 2 different databases.
What i got (simplified):
model.py
class Overeenkomst(models.Model):
park = models.IntegerField(blank=False, null=False, default='0')
object = models.IntegerField(blank=False, null=False, default='0') # ID from model second database
date_start = models.DateField()
date_end = models.DateField()
class Object(models.Model):
id = models.IntegerField(db_column='ID', primary_key=True) # Field name made lowercase.
nummer = models.IntegerField(db_column='NUMMER', blank=True, null=True) # Field name made lowercase.
omschrijving = models.CharField(db_column='OMSCHRIJVING', max_length=50, blank=True, null=True) # Field name made lowercase.
idobjectsoort = models.IntegerField(db_column='IDOBJECTSOORT', blank=True, null=True) # Field name made lowercase.
idobjecttype = models.IntegerField(db_column='IDOBJECTTYPE', blank=True, null=True) # Field name made lowercase.
(.....)
class Meta:
managed = False
db_table = 'OBJECT'
unique_together = (('nummer', 'idpark', 'id'), ('id', 'idpark', 'idobjecttype', 'idobjectsoort', 'dubbelboeking'), ('code', 'id'),)
def __str__(self):
return self.omschrijving
view.py
def ovk_overview(request):
ctx={}
overeenkomsten =models.Overeenkomst.objects.filter(park=request.session['park_id'])
ovk = []
for overeenkomst in overeenkomsten:
obj = models.Object.objects.using('database2').filter(pk=overeenkomst.object).values('omschrijving')
##### Here I Missing a part #####
ctx['overeenkomsten'] = ovk
return render(request, 'overeenkomsten/overzicht.html', context=ctx)
overzicht.html
{% extends "base.html" %}
{% load static %}
{% block content %}
<table class='table table-sm'>
<tr>
<th>#</th>
<th>Object</th>
<th>Start datum</th>
<th>Eind datum</th>
<th> </th>
</tr>
{% for ovk in overeenkomsten %}
{{ ovk }}::
<tr>
<td>{% now 'Y' %}{{ ovk.park }}{{ovk.object}}{{ovk.id}}</td>
<td>{{ ovk.object.omschrijving}}</td>
<td>{{ ovk.date_start|date:"d-m-Y" }}</td>
<td>{{ ovk.date_end|date:"d-m-Y" }}</td>
<td><button class="btn btn-primary">Download pdf</button></td>
</tr>
{% endfor %}
</table>
{% endblock %}
I have tried to use list() and chain() (as answer https://stackoverflow.com/a/8171434) but then i get only the values of the Object model and nothing from the Overeenkomsten model.
I hope someone has a answer/idea for me.

You could do something like this.
views.py
from django.forms.models import model_to_dict
def ovk_overview(request):
ctx={}
overeenkomsten =models.Overeenkomst.objects.filter(park=request.session['park_id'])
ovk = []
for overeenkomst in overeenkomsten:
overeenkomst_dict = model_to_dict(overeenkomst)
obj = models.Object.objects.using('database2').get(pk=overeenkomst.object) #Assumed there is always an obj. If not, change accordingly.
overeenkomst_dict['omschrijving'] = obj.omschrijving
ovk.append(overeenkomst_dict)
ctx['overeenkomsten'] = ovk
return render(request, 'overeenkomsten/overzicht.html', context=ctx)
overzicht.html
{% extends "base.html" %}
{% load static %}
{% block content %}
<table class='table table-sm'>
<tr>
<th>#</th>
<th>Object</th>
<th>Start datum</th>
<th>Eind datum</th>
<th> </th>
</tr>
{% for ovk in overeenkomsten %}
{{ ovk }}::
<tr>
<td>{% now 'Y' %}{{ ovk.park }}{{ovk.object}}{{ovk.id}}</td>
<td>{{ ovk.omschrijving }}</td>
<td>{{ ovk.date_start|date:"d-m-Y" }}</td>
<td>{{ ovk.date_end|date:"d-m-Y" }}</td>
<td><button class="btn btn-primary">Download pdf</button></td>
</tr>
{% endfor %}
</table>
{% endblock %}

First, a warning. Don't call fields object or id. They aren't Python reserved words, but using them overlays the meanings normally supplied by Python. It's confusing as hell, and in the case of object in particular, is also likely to cause you a world of pain at a later date if you start using Class-based views and Mixins. So call them something_id, or just obj.
OK. An idea ... yes. It depends on having enough memory to convert the main queryset into a list of objects. Then you "annotate" the objects in the first list, with the data from the corresponding objects in the second database.
I've replaced object with other_id in the following, because I simply couldn't think about it with the original name. It was like BLUE
printed in RED ink.
# query the first DB. You might want to chheck that the length
# of the query is sensible, and/or slice it with a maximum length
overeenkomsten = models.Overeenkomst.objects.filter(park=request.session['park_id'])
if overeenkomsten.count() > MAX_OBJECTS: # shouldn't ever happen
# do something to save our server!
overeenkomsten = overeenkomsten[:MAX_OBJECTS]
# fetch all the data
overeenkomsten = list( overeenkomsten )
# get the required data from the other DB.
# One query, retaining pk to tie the two together.
# avoids N queries on second DB
other_db_ids = [ x.other_id for x in overeenkomsten ] # was x.object
data_from_other_db = models.Object.objects.using('database2'
).filter(pk__in = other_db_ids
).values_list('pk', 'omschrijving'
)
# convert to a dict. This way for clarity and because I can't remember the dict method
#for converting a list of key/value pairs into a dict.
omschrivings = {}
for k,v in data_from_other_db:
omschrivings[k] = v
# "Annotate" the objects from the first query with the data from the second.
# It's read-only so no need to worry about saving it should somebody update it.
# (but you could subclass the save method if it wasn't)
for obj in overeenkomsten:
setattr( obj, 'omschriving', omschrivings[ obj[x.other_id] ] )
And in the template, just
<td>{{ ovk.omschrijving}}</td>

Related

Django - Problem with Model Manager - Query

I'm still a beginner and I'm stuck in a challenging spot for me.
I can't get data from a foreign key table to be inserted "correctly" into a column of a ListView.
I basically want to create a list view of a table (FeatureFilm). This also works. But in one column I get information from another table and here I get data, but not the one that belongs to the particular table row.
Here are my models. The table I want to show is "FeatureFilm" model. This model is inherited from my base Project class "ProjectBaseModel".
Then there is another table "CompanyInvolved Model". This is attached to the FeatureFilm table with a foreign key (feature_id).
So movies are stored (FeatureFilm) and different companies are involved in the creation process of the movies. (CompanyInvolved)
class ProjectBaseModel(models.Model):
title = models.CharField("Titel", max_length=100, blank=False, unique=True)
leading_postproduction_id = models.ForeignKey(
Company,
verbose_name="Federführende Postproduktion",
on_delete=models.SET_NULL,
blank=True,
null=True,
)
phase = models.CharField(choices=post_phase, max_length=30, blank=True, null=True)
former_title = models.CharField("Titel, ehemalig", max_length=100, blank=True)
title_international = models.CharField(
"Titel, international", max_length=100, blank=True, null=True, unique=True
)
class FeatureFilm(ProjectBaseModel):
class Meta:
verbose_name = "Kinofilm"
verbose_name_plural = "Kinofilme"
ordering = ["title"]
class ProductionManager(models.Manager):
def get_production(self):
return (
super()
.get_queryset()
.filter(company_role="Produktion", is_production_list=True)
.values_list("company_involved__name")
)
class CompanyInvolved(models.Model):
feature_id = models.ForeignKey(
FeatureFilm,
on_delete=models.CASCADE,
null=True,
blank=True,
)
tv_movie_id = models.ForeignKey(
TvMovie, on_delete=models.CASCADE, null=True, blank=True
)
company_role = models.CharField(
choices=company_role,
max_length=15,
blank=True,
help_text="Produktion, Co-Produktion, Kinoverleiher, Sender, Weltvertrieb",
)
company_involved = models.ForeignKey(
Company,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
is_production_list = models.BooleanField(
default=False,
verbose_name="Produktion in Liste",
)
productionmanager = ProductionManager()
def __str__(self):
return "#" + str(self.pk)
class Meta:
verbose_name = "Produktion, Co-Produktion, Verleih, Sender, Weltvertrieb"
verbose_name_plural = "Produktion, Co-Produktion, Verleih, Sender, Weltvertrieb"
ordering = ["pk"]
I basically wanted to generate the output now via the template. I can iterate the lines with the template for loop. But I also learned that more complex queries don't work in the DjangoTemplate language, or simply don't belong there.
I don't need every row of data from the CompanyInvolved, but only the company_role = "Production" and is_production_list = True.
A combined "Where" clause in the template now nice, but doesn't exist, so I built myself a MODEL MANAGER (ProductionManager) that does this filtering in the model.
here is the View:
class FeatureListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
permission_required = "project.can_access_featurefilm_list"
model = FeatureFilm
template_name = "project/feature-list.html"
def handle_no_permission(self):
return redirect("access-denied")
ans here is the depending snippet in the template:
<tbody>
{% for project in object_list %}
<tr>
<td>{{ project.title }}</td>
<td>{{ project.companyinvolved_set.get_production }}
<br>
</td>
<td>{% if project.program_length_planned %}
{{ project.program_length_planned }}
{% endif %}
</td>
<td>{{ project.global_shooting_resolution }}</td>
<td>{{ project.global_resolution_theatrical }}</td>
<td>{% if project.hdr == 1%}
ja
{% else %}
nein
{% endif %}
</td>
<td>{{ project.stafflist.postproduction_supervisor_id.username }}</td>
<td>{% if project.phase %}
{{ project.phase }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
so I iterate over each movie project with {% for project in object_list %} and then I want to show in my "problem" column the company that has the role production and since there can be more than one, the one that was previously marked by the user for the lists view - > is_production_list = TRue and then the output should come: {{ project.companyinvolved_set.get_production }}.
The result is going in the right direction, but it is still not finally correct. I get the CompanyINvolved data and these are also filtered by company_role = "Production" and is_production_list = True , but these values are now displayed to me each time in each individual Row the same, I get so not per ROW the associated productions but simply each time ALL. I'm missing the reference to the FeatureFilm object, but I don't know how to get this now, or where to put this reference now?
changed my template:
<tbody>
{% for project in object_list %}
<tr>
<td>{{ project.title }}</td>
<td>
!! {{ project.production_companies.company_involved.name}}!!
<br>
</td>
<td>{% if project.program_length_planned %}
{{ project.program_length_planned }}
{% endif %}
</td>
<td>{{ project.global_shooting_resolution }}</td>
<td>{{ project.global_resolution_theatrical }}</td>
<td>{% if project.hdr == 1%}
ja
{% else %}
nein
{% endif %}
</td>
<td>{{ project.stafflist.postproduction_supervisor_id.username }}</td>
<td>{% if project.phase %}
{{ project.phase }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
I think your code is not working because get_production calls super().get_queryset(), which returns a new QuerySet, without any filters applied, not based on the Queryset in the reverse look up in companyinvolved_set
Your best bet is to add a #property annotation to access the filtered list through your FeatureFilm model
#property
def production_companies(self):
return [company for company in self.companyinvolved_set.all() if company.company_role == 'Produktion' and company.is_production_list]
Then add this to your template:
<td>{% for production in project.production_companies %}{{production.company_involved.name}}{% endfor %}</td>
And to prevent a lot of queries, change the get_queryset method in your view to this:
def get_queryset(self):
queryset = FeatureFilm.objects.prefetch_related('companyinvolved_set').all()
return queryset

Can i compare DateTimeFields with the tag "if"?

I have this issue and I can't see why it isn't working. I have this model:
class Tweet(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1, on_delete=models.CASCADE)
content = models.TextField(validators=[validate_content])
updated = models.DateTimeField(auto_now=True)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.pk)
And I have this "for" where I call the objects of the model:
...
{% for t in object_list %}
<tr>
<td>{{ t.user }}</td>
<td>{{ t.content }}</td>
{% if t.updated == t.timestamp %}
<td>Created: {{ t.updated| timesince }} </td>
{% else %}
<td>Updated: {{ t.updated| timesince }} </td>
{% endif %}
...
I want to show the first message if the fields have the same content, which should be true if the object has just been created, but it shows the second message.
I have already checked in the admin panel the content of the fields and its the same in both, so I don't know what could be.
auto_now=True and auto_now_add=True each uses datetime.now() to obtain a timestamp, which always differ by a small fraction of a second because they are executed separately, so your the values of your updated and timestamp fields are never going to be equal.
You can instead get the timedelta between the two timestamps with timesince and if it's 0 minutes, it can be practically considered as newly created:
{% if t.timestamp | timesince:t.updated == "0 minutes" %}

Adding a related object as a context value back to a rendered view in Django

I have a query I can run in a shell and get the right answer given my models and the way the relations work:
# And let's see each business's employees:
employees=Employee.objects.filter(business_locations__business=business).distinct()
This works great, I can get set that to employees and have all the employees that work for a business. The trouble is I am not sure a clean way to figure this out when trying to actually use this model and render a list view from it. If you see the list.html code (last snippet) you can see where I am trying to provide the number of employees working for a business in the list and then ideally provide a link to edit the employees using a <-- --> arrow widget thingy.
I know I have to do this in the view right below, but I wasn't sure the cleanest way. Best I could come up with involved a for loop and a map of ids?
I have a view method: in my views.py:
def business_list(request):
object_list = Business.objects.all()
paginator = Paginator(object_list, 25) # 3 posts in each page
page = request.GET.get('page')
#make a map into the object list some how so I can have this data at the end...
try:
business = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer deliver the first page
business = paginator.page(1)
except EmptyPage:
# If page is out of range deliver last page of results
business = paginator.page(paginator.num_pages)
return render(request, 'ipaswdb/group/list.html', {'page':page, 'business':business, 'object_list':object_list})
Then my render view template for that view is this list.html:
{% extends "ipaswdb/base.html" %}
{% block title %}Businesses{% endblock %}
{% block content %}
<h1>Businesses</h1> <p> Search: </p>
{% with object_list.count as total_businesses %}
<h2>
There are {{ total_businesses }} businesses
</h2>
{% endwith %}
<table class="table">
<tr>
<th> ID</th>
<th> Business Name</th>
<th> Comments</th>
<th> Total employees</th>
<th>Edit</th>
<th>Term</th>
{% for group in groups %}
<tr>
<td>{{ business.id }}</td>
<td>{{ business.business_name }}</td>
<td> {{ business.notes|truncatewords:30|linebreaks }}</td>
<!-- How do i do this? -->
<td> {{ business.business_total }}</td>
<td> editbutton</td>
<td>termbutton</td>
</tr>
{% endfor %}
</table>
{% include "pagination.html" with page=groups %}
{% endblock %}
Models:
class Employee(models.Model):
first_name = models.CharField(max_length = 50)
business_locations = models.ManyToManyField('BusinessLocations', through='EmployeeLocations')
class EmployeeLocations(models.Model):
employee = models.ForeignKey('Employee', on_delete=models.CASCADE)
business_location = models.ForeignKey('BusinessLocations', on_delete=models.CASCADE)
created_at=models.DateField(auto_now_add=True)
updated_at=models.DateField(auto_now=True)
def __str__(self):
return self.provider.first_name
class BusinessLocations(models.Model):
address = models.ForeignKey('Address', on_delete= models.SET_NULL, null=True)
business = models.ForeignKey('Business', on_delete=models.CASCADE)
doing_business_as = models.CharField(max_length = 255)
created_at=models.DateField(auto_now_add=True)
updated_at=models.DateField(auto_now=True)
def __str__(self):
return self.doing_business_as
class Business(models.Model):
business_name = models.CharField(max_length=50)
I had help with these from this stackoverflow question here:
Giving a model knowledge of a many-to-many related model in django

Django - iterating a queryset produces hundreds of queries

I have a model where a "variable template" is defined, and attached to every Location model in the database, producing a 2-dimensional array of instances of these variables.
class Location(models.Model):
name = models.CharField(max_length=4, blank=False, unique=True)
class LocationVariableDefinition(models.Model):
name = models.CharField(max_length=32, blank=False, unique=True)
type = models.CharField(max_length=60, default="text")
default_value = models.CharField(max_length=250, blank=True, default="[UNSET]")
class LocationBasedVariable(models.Model):
location = models.ForeignKey(Location)
definition = models.ForeignKey(LocationVariableDefinition, default=None)
value = models.CharField(max_length=250, blank=True)
To edit these variables, I have a single page with a table, variable names down the side, and locations along the top. I added a new SimpleTag to allow me to access values from a dict-within-a-dict, and the view produces this dict:
class VarListView(TemplateView):
template_name = "lbv-list.html"
def locations(self):
return Location.objects.all()
def locvars(self):
dict = {}
for v in LocationBasedVariable.objects.all():
dict.setdefault(v.location.id, {})
dict[v.location.id][v.definition.id] = v.value
return dict
def locvarnames(self):
return LocationVariableDefinition.objects.all()
and then in the template:
<table class="table table-striped table-condensed striped">
<thead>
<tr>
<th> </th>
{% for loc in view.locations %}
<th>{{ loc.name }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for lv in view.locvarnames %}
<tr>
<th>{{ lv.name }}</th>
{% for loc in view.locations %}
<td>{% get_dictitem2 view.locvars loc.id lv.id%}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
Which all works, except that with 3 locations and 5 variables, I get 475 SQL queries, taking about 3 seconds to run on my laptop.
What can I do to improve this? I always thought that the queryset would be cached, but the SQL logged in the console seems to be fetching the same sets over and over. Is there a hint I can give Django that the result won't have changed, or a way to do each query only once?
Or should I be just passing a sorted queryset to the template, and figuring out the rows and columns in there? (the way I would normally do that would involve keeping state in the template for the last location to compare with the current, and I don't think I can do that in a template)
The usual approach would be to evaluate the methods once, and add the results to the template context in get_context_data.
class VarListView(TemplateView):
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(VarListView, self).get_context_data(**kwargs)
context['locations'] = self.locations()
context['locvarnames'] = self.locavarnames()
context['locvars'] = self.locvars()
return context
Then, in your template, remove the view prefix e.g. locations instead of view.locations.
Aside from changing .id to _id, you should also be looking at what you actually need to return, since your for loop only ever uses the id you could just use values to filter this to start with
query_gives_locations.values('id')
You have to keep getting .locvars every time so you can move this outside with with
{% with locvars=view.locvars %}
{% for loc in view.locations %}
<td>{% get_dictitem2 locvars loc_id lv_id%}</td>
{% endfor %}
{% endwith %}
You have a couple of options here, but I think the best is to use the _id property for your foreign key relations. Django stores the id of the foreign keys on the model in question, so in your case you could replace your loop with:
for v in LocationBasedVariable.objects.all():
dict.setdefault(v.location_id, {})
dict[v.location_id][v.definition_id] = v.value
This means that you won't have to query the related tables just to get the ID.
Also, it looks like you are invoking {% get_dictitem2 view.locvars loc.id lv.id%} for each item in view.locvarnames which in turn will go to the database, load all of your LocationBasedVariable and populate that dict.
I might be tempted to cache that in the view so you only populate it once:
class VarListView(TemplateView):
template_name = "lbv-list.html"
_location_based_variable_cache = None
def locations(self):
return Location.objects.all()
def locvars(self):
if not self._location_based_variable_cache:
self._location_based_variable_cache = {}
for v in LocationBasedVariable.objects.all():
self._location_based_variable_cache.setdefault(v.location_id, {})
self._location_based_variable_cache[v.location_id][v.definition_id] = v.value
return self._location_based_variable_cache
def locvarnames(self):
return LocationVariableDefinition.objects.all()

Django Models: getting data using a filter, empty

I'm trying to list all data on a page from table 'points_points' where user_id is the user logged in. However, the object is blank when displayed no data is listed. I 100% have data in the DB for the user. Why does nothing display? Can anyone spot any mistakes below?
models.py
class PointsManager(models.Manager):
def points_list(self,thisUser):
list = Points.objects.filter(user=thisUser)
return list
class Points (models.Model):
user = models.ForeignKey(User)
points = models.IntegerField(verbose_name=("Points"), default=0)
created = models.DateTimeField(("Created at"), auto_now_add=True)
updated = models.DateTimeField(verbose_name=("Updated at"), auto_now=True)
objects = PointsManager()
class Meta:
verbose_name = ('Point')
verbose_name_plural = ('Points')
views.py
#login_required
def history(request):
thisPoints = Points.objects.points_list(request.user)
context = {'points':thisPoints}
return render_to_response('points/history.html', context, context_instance=RequestContext(request))
template.py
<h1>|{{ points }}|</h1>
{% for item in points.Points_items.all %}
<tr>
<td>{{ item.points }}</td>
</tr>
{% endfor %}
Try this
def points_list(self,thisUser):
return super(PointsManager, self).get_query_set().filter(user=thisUser)
Your template doesn't looks right..
{% for item in points %}
<tr>
<td>{{ item.points }}</td>
</tr>
{% endfor %}

Categories

Resources