"Other side" of a ForeignKey relation - django

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

Related

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 multiple choice and multi-select field in model and form

I am learning Django and have hit an inssue that I don't know what to Google for. I have the standard User model and a Profile model that points into the User model via a OneToOne field. That part plus the profile's Form and the CBVs all work.
A profile holder can have one or more professional affiliations. This is not for a union but my Dad, for example, as a welder, was affiliated with: "The Iron Workers", "Steam Fitters", "Boiler Makers", "Pipe Fitters" and a couple other specific unions that used welders. So if I want the profile and form to allow multiple choices and multiple selected results for affiliations then I see that you might use
class Profile(models.Model):
GROUPS = (
('IW', 'Iron Workers'),
('PF', 'Pipe Fitters'),
...
)
affiliation = models.CharField(
max_length=3,
choices=RANK,
default='U',
blank=True,
null=True,
)
...
In the associated form it looks like you use a
GROUPS = (
('IW', 'Iron Workers'),
('PF', 'Pipe Fitters'),
...
)
affiliation = forms.MultipleChoiceField(
choices=GROUPS
)
What if I want a db table of these choice items instead of tuples? If I have to add to a list of choices then editing the code might not be the most convenient response. Maybe the list of groups should be in the Django admin area.
How do I tell the model and form to look in a separate db table for the possible values and then how do I modify the model and form to allow for zero or more group affiliations?
You need to create a many-to-many relationship to a new model, let's call it Skill, that represents the available skills that can be dynamically created.
class Profile(models.Model):
# ...
skills = models.ManyToManyField(
to='myapp.Skill', # use a string in the format `app_name.model_name` to reference models to avoid issues using the model before it was defined
related_name='user_profiles', # the name for that relation from the point of view of a skill
)
class Skill(models.Model):
name = models.CharField(max_length=255)
abbreviation = models.CharField(max_length=3)
Then in your form you can just add that many-to-many field skills. It will by default be a multiple choice field in the django admin.
The relation can have zero to n entries for each pair of profile and skill. In other words, every user can have zero to n skills and vice versa.
To allow for a default value you will have to write a custom migration that creates that default entity of a Skill to make sure it is in the database at runtime.
In my case, I solved this way with a select2 list option.
It returns in the view a list of option selected.
FORMS
class PrecoattivoForm(forms.Form):
entrate = AccertamentiEnte.objects.all().values("entrata_provvedimento").distinct()
lista = []
for i in entrate:
lista.append([i.get('entrata_provvedimento'),i.get('entrata_provvedimento')])
entrata_accertamento= forms.MultipleChoiceField(choices=lista, required=False)
TEMPLATE
<div class="col-xl-10">
<selectclass="js-example-placeholder-multiple col-sm-12"
multiple="multiple"
name="{{ form.entrata_accertamento.name }}"
id="{{ form.entrata_accertamento.id_for_label }}">
{% for entrata in form.entrata_accertamento %}
<option {% if form.name.value != None %}value="
{{form.entrata_accertamento.value|upper }}"{% endif %}>{{ entrata }}</option>
{% endfor %}
</select>
</div>

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.

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

Django left join m2m field

Here's my Model:
class User(models.Model):
pass
class Item(models.Model):
pass
class ItemVote(models.Model):
user = models.ForeignKey(User)
item = models.ForeignKey(Item)
vote = models.BooleanField()
I want to retrieve a list of Items, and I want to know if the current user has voted for each Item. How do I alter my query object so that it will generate sql similar to:
SELECT ...
FROM items
LEFT OUTER JOIN item_votes ON (item_votes.user_id = ? AND
item_votes.item_id = items.id)
You cannot in plain django queries. This is a many to many relation ship, you can even specify it more clearly by doing something like this:
class Item(models.Model):
votes = models.ManyToManyField(User, through='ItemVote', related='votedItems')
In a Many To Many relationship, we can speak of related sets (because there are multiple objects). While django can filter on related sets, something like:
Item.objects.filter(votes=request.user)
this will return you all Items that a user has voted for. However when you will list the .votes attribute in those objects, you will get all users that have voted for that item. the filtering is for the original object, never for the related set.
In a purely django way, you can expand my previous Item class to:
class Item(models.Model):
votes = models.ManyToManyField(User, through='ItemVote', related='votedItems')
def markVoted(self, user):
self.voted = user in self.votes
And then call this method for every object. This will however create an additional query to the votes set for every object (and no, select_related does not work for many to many relationships).
The only way to solve this is to add some SQL to your queryset using the extra method, like this:
Item.objects.extra('voted' : 'SELECT COUNT(*) FROM app_itemvote WHERE app_itemvote.item_id = app_item.id AND app_itemvote.user_id=%d'%request.user.pk)
Return ItemVote items:
items = ItemVote.objects.filter(user=user)
use in Django template:
{% for i in items %}
item: {{ i.item }}
voted: {{ i.voted }}
{% endfor %}