How to display related content of an object - django

I have a Contact class that can have many Conversations. But each conversation can belong to a single Contact.
So its a One-To-Many relationship.
class Contact(models.Model):
first_name = models.CharField()
last_name = models.CharField()
class Conversation(models.Model):
contact = models.ForeignKey(Contact)
notes = models.TextField()
def __unicode__(self):
return self.notes
Now when I pass in the contacts to the template, I would like to have one field that shows the last conversation for the contact.
contacts= profile.company.contact_set.all()[:10]
calls = []
for c in contacts:
calls.append(max(c.conversation_set.all()))
And I pass them in like this:
vars = {'contacts' : contacts, 'calls': calls}
variables = RequestContext(request, vars)
return render_to_response('main_page.html', variables)
In the template I came up with this magic:
{% for idx, val in contacts %}
<tr>
<td>...
</td>
<td>
{% if calls %}
{{ calls.idx }}
{% endif %}</td>
</tr>
{% endfor %}
This doesn't show anything for calls. But if I replaced calls.idx with calls.0 I get the first one displayed.
What am I doing wrong? Beside the fact that it could be done probably much easier than that. :) I am open for better ways of doing it.

You can't do this sort of indirect lookup in the template language - calls.idx will always refer to an attribute idx belonging to calls.
However I think a better solution is to add the call value directly onto the contact object - don't forget that in Python objects are dynamic, so you can annotate anything you like onto them.
for c in contacts:
c.max_call = max(c.conversation_set.all())
As an aside, does that max really work? I'm not sure what it would be doing the comparison based on. An alternative is to define get_latest_by in your Conversation model, then you can avoid this loop altogether and just call {{ contact.conversation_set.get_latest }} in your template for each contact through the loop.
(Note this will still be pretty inefficient, there are various ways of getting the whole set of max conversations in one go, but it will do for now.)

class Contact(models.Model):
# your code here
#property
def last_conversation(self):
try:
return self.conversation_set.order_by("-some_date_or_order_field")[0]
except IndexError:
return None
Then you don't have to care about this in your view and just need to call "c.last_contact" in your template.

Related

Joining Models And Passing To Template

I have multiple related models and on a single page I need to access data from different models on a variety of conditions. Most of this data is accessed without a form as only a few fields need to be manipulated by the user.
To give you an idea of how interconnected my Models are:
class User(models.Model):
first = models.ManyToManyField(First)
second= models.ManyToManyField(Second)
class First(models.Model):
[...]
class Second(models.Model):
first = models.ManyToManyField(First)
class Third(models.Model):
first = models.ForeignKey(First)
class Fourth(models.Model):
user = models.ForeignKey(User)
second = models.ForeignKey(Second)
third = models.ForeignKey(Third)
class Fifth(models.Model):
fourth = models.ForeignKey(Fourth)
class Sixth(models.Model):
fifth = models.ForeignKey(Fifth)
I'm having a lot of difficulty passing this data to my template and displaying it in efficiently. I originally displayed it to the user in a horrendous way as I just wanted to see if my data was accessible/displaying properly. This is an example of some of the dodgy code I used to test that my data was being passed properly:
{% for second in user.second.all %}
{% for first in second.first.all %}
[...]
{% for fourth in user.fourth.all %}
[...]
{% if fourth.first_id == first.id and fourth.second_id == second.id %}
{% if fourth.checked == True %}
[...]
{% else %}
[...]
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
[...]
Isn't it an abomination, and it goes deeper. Now I am trying to reformat this. I know that my processing should be done within my view rather than my template. The thing is I can't just filter out a lot of the data in my views as I need a lot of it in the template at different times (eg I might have 20 pieces of data in a model and I need to access all 20, but some when the id match, some when the id and type match etc. It's a lot of template side logic - I think that's bad).
This is the kind of thing I have been trying to do so far but am having no luck:
second = Second.objects.filter(user__id=user.id) .select_related('First')
Any help with how to join/pass all these models to my view and access the data without the nested nested loops, or even just pointers on how to approach this would be appreciated. I's also unsure if I should be aiming to join as many models as possible then pass into my view or pass many separate models.
Thank you.
you are serializer the data use a drf serializer you will need to use a nested serializer
example for nested serializer
class HospitalsSerializer(serializers.ModelSerializer):
class Meta:
model = Hospitals
fields = '__all__'
class HealthProductSerializers(serializers.ModelSerializer):
hospital_list = HospitalsSerializer(many=True)
class Meta:
model = HealthProduct
exclude = ("created_at", "updated_at")

django count specific rows in queryset

class Order(models.Model):
name = models.CharField(max_length=100)
# other fields..
user = models.ForeginKey(User)
old = models.BooleanField(default=False)
I want to display all the orders of a specific user, but I want to split them those which are "old" and the ones who are not.
So, currently I do in views.py:
orders = Order.objects.filter(user=user)
In template:
First table:
<table>
{% for order in orders %}
{% if not order.old %}
<tr>
<td>... </td>
</tr>
{% endif %}
{% endfor %}
</table>
And another table:
{% for order in orders %}
{% if order.old %}
<tr>
<td>...</td>
<tr>
{% endif %}
{% endfor %}
This way have some drawbacks, first, now I want to count how many of the orders are "old", to display this number in the template. I can't, unless I do another query.
Is it possible to annotate(number_of_old=Count('old'))? Or I have to do another query?
So what would be the best?
1. Do two queries, one with old=False, another with old=True, and pass two querysets to the template. And use |len filter on the querysets
2. Do one query like this and split them somehow in python? That will be less convenient as I have a similar structures which I want to split like that.
And should I call the DB .count() at all?
EDIT:
If I would write my model like this:
class Order(models.Model):
name = models.CharField(max_length=100)
# other fields..
user = models.ForeginKey(User)
old = models.BooleanField(default=False)
objects = CustomManager() # Custom manager
class CustomQueryset(models.QuerySet):
def no_old(self):
return self.filter(old=False)
class CustomManager(models.Manager):
def get_queryset(self):
return CustomQuerySet(model=self.model, using=self._db)
Is this template code produce one or two queries ?
{% if orders.no_old %}
{% for order orders.no_old %}
...
{% endfor %}
{% endif %}
You can't do any annotations, and there is no need to make .count() since you already have all the data in memory. So its really just between:
orders = Order.objects.filter(user=user)
old_orders = [o for o in orders if o.old]
new_orders = [o for o in orders if not o.old]
#or
old_orders = Order.objects.filter(user=user, old=True)
new_orders = Order.objects.filter(user=user, old=False)
In this specific scenario, I don't think there will be any performance difference. Personally I will choose the 2nd approach with the two queries.
A good read with tips about the problem: Django Database access optimization
Update
About the custom Manager which you introduce. I don't think you are doing it correctly I think what you want is this:
class CustomQueryset(models.QuerySet):
def no_old(self):
return self.filter(old=False)
class Order(models.Model):
name = models.CharField(max_length=100)
# other fields..
user = models.ForeginKey(User)
old = models.BooleanField(default=False)
#if you already have a manager
#objects = CustomManager.from_queryset(CustomQueryset)()
#if you dont:
objects = CustomQueryset.as_manager()
So having:
orders = Order.objects.filter(user=user)
If you do {% if orders.no_old %} will do another query, because this is new QuerySet instance which has no cache..
About the {% regroup %} tag
As you mention, in order to use it, you need to .order_by('old'), and if you have another order, you can still use it, just apply your order after the old, e.g. .order_by('old', 'another_field'). This way you will use only one Query and this will save you one iteration over the list (because Django will split the list iterating it only once), but you will get less readability in the template.

Django template limit output of the for cycle

So I have a template:
{% for Chapter in latest_chapter_list %}
{% ifequal Chapter.manga|truncatechars:20 Manga.name|truncatechars:20 %}
{{Chapter.chapter}}
{% endifequal %}
{% endfor %}
models:
class Manga(models.Model):
name = models.CharField(max_length=20,help_text='Name of the comic/manga')
class Chapter(models.Model):
manga = models.ForeignKey(Manga)
chapter = models.IntegerField(default=1, help_text='Number of a chapter')
So what I want is that the template would only display 5 items instead of all items that pass the if. In normal code I would add a temp value that counts each addition and later on resets, but I'm new to django and I don't know how to approach this.
Also I can't figure out why my if only works if I cut both names to equal length, even though they should be the same length, shouldn't they?
Also my views:
def index(request):
latest_item_list = Manga.objects.all().order_by('-added_on')[:5]
latest_chapter_list = Chapter.objects.all().order_by('-chapter')
context = {'latest_item_list': latest_item_list,
'latest_chapter_list': latest_chapter_list
}
return render(request, 'Item/index.html', context)
any help or tips would be appreciated!
EDIT, SOLUTION: made new filtered list in views:
latest_chapter_list_short = Chapter.objects.filter(chapter__lt=6)
and iterated through it instead of the full list!
Django has limited functionality in its templates so it's easier to only iterate over the elements that you wish to display. For example, you can prepare the data in the view to only contain five elements:
latest_item_list = Manga.objects.all().order_by('-added_on')[:5]
chapters = Chapter.objects.all().order_by('-chapter')
latest_chapter_per_mange = compute_chapters_per_mange(chapters, latest_item_list)
where compute_chapters_per_mange determines the chapters per Manga model.
Your second question: Django is now comparing the string representation of a Manga object to Manga.name which may not be the same. This could explain why you need truncatechars although I don't fully see why. If you define a __unicode__ method on your Manga model then you can specify how an instance of that model should be displayed as a string (i.e., self.name).

Django multi-model: tracing relationships

I have a multi model with different OneToOne relationships from various models to a single parent. Consider this example:
class Place(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
...
class Restaurant(models.Model):
place = models.OneToOneField(Place)
...
class Shop(models.Model):
place = models.OneToOneField(Place)
...
Regardless if the above model makes sense in real life scenario, how can someone identify if an object of Place has a relationship to any of the other models, in django view?
In a template, you can say
{% if place.restaurant %}
<!-- stuff goes here -->
{% endif %}
The reason for this is that OneToOneField entries actually create an attribute in the model they are referencing. In normal Python code, saying place.restaurant where no restaurant is defined will throw an exception, but templates will swallow such exceptions.
If you do need to do something like this in Python code, the simplest-to-understand way is to wrap it in a try/except block:
place = Place.objects.all()[0]
try:
restaurant = place.restaurant
# do something with restaurant
except ObjectDoesNotExist:
# do something without restaurant
EDIT: As I say in my comment, if you only want a Place to be either a Restaurant or a Shop but never both, then you shouldn't be using OneToOneField and should instead use model inheritance.
Assuming that a Place could have two or more other possibilities, I recommend doing something like this:
class Place(Model):
# define your fields here
# you could generate this automatically with trickery
OTHER_MODELS = ["restaurant", "shop"]
#property
def relationships(self):
if not hasattr(self, "_relationships"):
self._relationships = {}
for attr in OTHER_MODELS:
try:
self._relationshops[attr] = getattr(self, attr)
except ObjectDoesNotExist:
pass
return self._relationships
The above would let you say place.relationships and get back a dictionary that looked like
{"restaurant": <Restaurant Object>, "shop": <Shop Object>}
although one or both of those might be missing, depending on whether they exist. This would be easier to work with than having to catch a potential exception every time you reverse the OneToOneField relationship.
Because Restaurant has a foreign key pointing into Place, it leaves a related name field on the class, so that the pointed-to class (Place) can find its contents:
# python
import yourproject.settings
from django.db.models.base import ObjectDoesNotExist
try:
r = place.restaurant_set.get()
do_something( r.restaurant_field )
except ObjectDoesNotExist:
print "place has no restaurant"
And from a template, assuming you have access to placeobject from your context somehow:
{% with placeobject.restaurant_set.get as r %}
{% if r %}
{{ r.restaurant_field }}
{% else %}
No restaurant
{% endif %}
{% endwith %}

Handling related models in Django for use in Django-Piston

I have setup like so (changed for simplicity)
class Author(models.Model)
name = models.CharField(max_length=100)
...
class Document(models.Model):
title = models.CharField(max_length=200)
content - models.TextField()
author = models.ForeignKey("Author", related_name="documents")
date_published = models.DateTimeField()
categories = models.ManyToManyField("Category")
class Category(models.Model):
name = models.CharField(max_length=100)
I'm pulling in the Author records but I only want to pull in related document records for each author that match specific criteria -- say, date_published and category.
I know the easy way to do this would be to pull in the records as a list of dictionaries using Author.objects.values(), looping through each record and running:
author['documents']=Document.objects.filter(categories__in=[category_list], date_published__year=year)`
However, this is being generated for django-piston, and it seems to be happiest (particularly if you're defining your own fields!) if you return a QuerySet object.
Part of this may be because I made changes to the base django-piston code. Basically, the current version of the code here overwrites the fields value. I changed this code so that I could change the fields value for a Handler based on the request (so I could provide more details if the request was for a specific resource).
So I guess my question is three-fold:
Is there a way to filter or somehow limit the subrecords of a record (i.e. filter documents for each author.documents)
If not, what is a functional way of doing this that also works with django-piston?
Is there some easier, better way to do what I'm trying to do (display all the authors without their documents if an id is not given, but displaying the sub-records if filtering to just one author)?
Clarification
Okay, just to be clear, here is the pseudocode that I want:
def perhaps_impossible_view(request, categories=None, year=None):
authors = Author.objects.all()
authors.something_magical_happens_to_documents(category__in=[categories], date_published__year=year)
return render_to_response('blar.html', locals(), RequestContext(request))
So that if I were to put it in a template, this would work without any modifications:
{% for a in authors %}
{% for d in authors.documents.all %}
{{ d.title }} is almost certainly in one of these categories: {{ categories }} and was absolutely published in {{ year }}. If not, .something_magical_happens_to_documents didn't work.
{% endfor %}
{% endfor %}
something_magical_happens_to_documents has to run and actually change the contents of documents for each author record. It seems like this should be possible, but perhaps it isn't?
Edited... or better... replaced! :)
That's true the authors without document matching won't be in the queryset so you will have to add them back after (I couldn't find a better way but maybe someone knows how to not remove them without using raw sql).
You get the full documents count of the authors because you don't use the queryset to get the document counts:
qryset = Author.objects.all()
qryset = qryset.filter(documents__categories__name__in = category_list).distinct()
qryset = qryset.filter(documents__date_published__year=year)
print(qryset) gives [<Author: Author object>] (if only 1 author matched all categories) and qryset[0].documents.count() will return only the number of documents matched (not all documents from the author - 2 in the case I tested and the author had 4 but only 2 matching all conditions).
If you use dict (.values()) instead of querysets in the steps above, you can't do that (I think) because dict won't have a documents field so:
qryset_dict = Author.objects.values()
qryset_dict = qryset_dict.filter(documents__categories__name__in = category_list).distinct().values()
qryset_dict = qryset_dict.filter(documents__date_published__year=year).values()
when you issue qryset_dict[0].documents.count() you receive an error:
AttributeError: 'dict' object has no attribute 'documents'
Now to add the filtered authors back you can do:
res = []
for a in Author.objects.all():
if a in qryset:
res.append([a,a.documents.count()])
else:
res.append([a,0])
and res will be a list with <authors> in 1st column and count of documents matching in 2nd column.
I know this is far from perfect... but if you are interested only in the count() of matching documents per author, I think you could find a better way using django aggregation and annotation or possibly make it with raw sql using a left join from authors to documents.
EDIT after Clarification in Question:
def possible_view(request, categories=None, year=None):
# you will pass these as parmeters of course
category_list = ['c2', 'c3']
year = 2010
qryset = Document.objects.filter(categories__name__in = category_list).distinct()
qryset = qryset.filter(date_published__year=year)
authors = Author.objects.all()
return render_to_response('blar.html', { 'result': qryset, 'authors': authors, 'categories': category_list, 'year': year }, RequestContext(request))
Template blar.html:
{% for a in authors %}
<b>{{a.name}}</b><br />
{% for d in result %}
{% if d.author.name == a.name %}
{{ d.title }} is almost certainly in one of these categories: {{ categories }} and was absolutely published in {{ year }}. If not, .something_magical_happens_to_documents didn't work.<br />
{% endif %}
{% endfor %}<br />
{% endfor %}
This will give you something not very pretty but with all authors and below each one, the list of their documents that fall within one of the category_list (OR condition, for AND, you need to filter the query for each category instead of using __in).
If the author has no document in the category_list, it wil be listed without documents below him.
Something like:
aut1
tit2 is almost certainly in one of these categories: ['c2', 'c3'] and was absolutely published in 2010. If not, .something_magical_happens_to_documents didn't work.
tit1 is almost certainly in one of these categories: ['c2', 'c3'] and was absolutely published in 2010. If not, .something_magical_happens_to_documents didn't work.
aut2
tit3 is almost certainly in one of these categories: ['c2', 'c3'] and was absolutely published in 2010. If not, .something_magical_happens_to_documents didn't work.
aut3