Django multi-model: tracing relationships - django

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 %}

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 : m2m relationship create two line instead of one

I have extended the UserModel this way :
# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
# add additional fields in here
credit = models.IntegerField(default=200)
follow = models.ManyToManyField('self', related_name='follow')
def __str__(self):
return self.username
But I am stuck as to how I should add/remove a follower. I have created a view with :
#login_required
def follow(request, user_id):
user = get_object_or_404(CustomUser, pk=user_id)
if CustomUser.objects.filter(follow=user.pk).exists():
request.user.follow.remove(user)
else:
request.user.follow.add(user)
return redirect('profil', user_id)
Issue :
Let's say request.user.pk is 1 and user_id is 2.
For the add part (in the else), I would expect a new line in database with from_customuser_id=1 and to_customuser_id=2 however, it creates two lines:
one with from_customuser_id=1 and from_customuser_id=2 as expected
one with from_customuser_id=2 and from_customuser_id=1 which I don't need.
And for the remove part (in the if), I would expect it to only remove the line
from_customuser_id=1 and from_customuser_id=2
But it removes the two lines.
I read the doc about django models relations but didn't found how to solve this issue.
Question :
How should I update my code in order to have the add method to only insert one line with from_customuser_id=1, from_customuser_id=2 and the remove method to only delete this line (assuming the current user have the id 1).
Not sure if it is relevant but for sake of completeness this is the related part of my urls.py :
path('follow/<int:user_id>', views.follow, name='follow'),
path('unfollow/<int:user_id>', views.follow, name='unfollow'),
And this is how I call them in templates :
{% if follow %}
<a href="{% url 'follow' user_profil.id %}">
Unfollow {{ user_profil.username }}
</a>
{% else %}
<a href="{% url 'unfollow' user_profil.id %}">
Follow {{ user_profil.username }}
</a>
{% endif %}
When you have a ManyToManyField it essentially creates a relationship between both the objects. This also allows you to do reverse lookups.
For example:
class Person(models.Model):
name = model.CharField(max_length=100)
class Pet(models.Model):
owners = models.ManyToMany(Person, related_name="pets")
name = model.CharField(max_length=100)
bob = Person.objects.create(name="Bob")
john = Person.objects.create(name="John")
kitty_kat = Pet.objects.create(name="Kitty Kat")
kitty_kat.owners.set([bob, john])
According to these models one pet can be owned by multiple people, and one person can have multiple pets. So if I do
bob.pets.all() # I get kitty kat
kitty_kay.owners.all() # I get bob & john
When this relationship is supposed to be on the same model, you end up creating two relationships. One as the normal one & one for the reverse.
For example:
class Person(models.Model):
name = model.CharField(max_length=100)
followers = models.ManyToManyField('self', related_name='follow')
bob = Person.objects.create(name="Bob")
john = Person.objects.create(name="John")
john.followers.add(bob)
bob.follow.all() # I get john... notice I use follow and not followers
john.followers.all() # I get bob
In order to avoid this you can pass symmetrical=False to the field and one one row will be created
followers = models.ManyToManyField('self', related_name='+', symmetrical=False)
Setting the related_name to anything starting with + will also prevent reverse lookups (which you don't need in this case)

Django query caching through a ForeignKey on a GenericRelation

Using a GenericRelation to map Records to Persons, I have the following code, which works correctly, but there is a performance problem I am trying to address:
models.py
class RecordX(Record): # Child class
....
class Record(models.Model): # Parent class
people = GenericRelation(PersonRecordMapping)
def people_all(self):
# Use select_related here to minimize the # of queries later
return self.people.select_related('person').all()
class Meta:
abstract = True
class PersonRecordMapping(models.Model):
person = models.ForeignKey(Person)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class Person(models.Model):
...
In summary I have:
RecordX ===GenericRelation===> PersonRecordMapping ===ForeignKey===> Person
The display logic in my application follows these relationships to display all the Persons mapped to each RecordX:
views.py
#rendered_with('template.html')
def show_all_recordXs(request):
recordXs = RecordX.objects.all()
return { 'recordXs': recordXs }
The problem comes in the template:
template.html
{% for recordX in recordXs %}
{# Display RecordX's other fields here #}
<ul>
{% for map in record.people_all %}{# <=== This generates a query every time! #}
<li>{{ map.person.name }}</li>
{% endfor %}
</ul>
{% endfor %}
As indicated, every time I request the Persons related to the RecordX, it generates a new query. I cannot seem to figure out how prefetch these initially to avoid the redundant queries.
If I try selected_related, I get an error that no selected_related fields are possible here (error message: Invalid field name(s) given in select_related: 'xxx'. Choices are: (none)). Not surprising, I now see -- there aren't any FKs on this model.
If I try prefetch_related('people'), no error is thrown, but I still get the same repeated queries on every loop in the template as before. Likewise for prefetch_related('people__person'). If I try prefetch_related('personrecordmapping'), I get an error.
Along the lines of this answer, I considered doing trying to simulate a select_related via something like:
PersonRecordMapping.objects.filter(object_id__in=RecordX.objects.values_list('id', flat=True))
but I don't understand the _content_object_cache well enough to adapt this answer to my situation (if even the right approach).
So -- how can I pre-fetch all the Persons to the RecordXs to avoid n queries when the page will display n RecordX objects?
Thanks for your help!
Ack, apparently I needed to call both -- prefetch_related('people', 'people__person'). SIGH.

"Other side" of a ForeignKey relation

I read many times this and this tutorials, but I cant understand, how to do the following:
models:
class Car(models.Model):
field1
field2
field3
class CarOptions(models.Model):
car = models.OneToOneField(Car, primary_key=True)
field4
field5
class CarPictures(models.Model):
car = models.ForeignKey(Car)
field6
field7
So, I need get all information about the car in one sql-query. How it wrote in docs:
car = get_object_or_404(Car, pk=car_id)
But here is a strange (it discribes like "other side" of a ForeignKey relation) poll.choice_set.all, that doesnt works with my code. Some copy-past code, sorry, there is no links in docs:
# Give the Poll a couple of Choices. The create call constructs a new
# choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a poll's choices) which can be accessed via the API.
>>> p = Poll.objects.get(pk=1)
# Display any choices from the related object set -- none so far.
>>> p.choice_set.all()
[]
# Create three choices.
>>> p.choice_set.create(choice='Not much', votes=0)
<Choice: Not much>
>>> p.choice_set.create(choice='The sky', votes=0)
<Choice: The sky>
>>> c = p.choice_set.create(choice='Just hacking again', votes=0)
# Choice objects have API access to their related Poll objects.
>>> c.poll
<Poll: What's up?>
# And vice versa: Poll objects get access to Choice objects.
>>> p.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
I havent got choice_set.all(). I add all information from the admin interface. With foreign keys all works well, but I need do a few sql-querys, not one. And in the docs they discribed it, like a one sql-query, but they have choice_set.all(). How its possible to do with my model? I need all information in the template (html), can you give me some example, how it works?
Thanks.
Related managers' names are automatically generated from model names. You have car.carpictures_set and car.caroptions (this isn't a "set" because it's a one-to-one relationship).
You can define your own related names:
class Car(models.Model):
...
class CarOptions(models.Model):
car = models.OneToOneField(Car, primary_key=True, related_name='options')
class CarPictures(models.Model):
car = models.ForeignKey(Car, related_name='pictures')
Then you'll have car.options and car.pictures.
Related objects reference
Let's say this is your view
cars = Car.objects.filter()
car_options = CarOptions.objects.filter()
car_pictures = CarPictures.objects.filter()
Here how it relates in html
{% for car in cars %}
{% for option in car.car_options %}
{% endfor %}
{% for picture in car.car_pictures %}
{% endfor %}
{% endfor %}

How to display related content of an object

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.