I am just following one of the Django tutorials verbatim and have encountered a problem with queries of x.id or .pk not returning any value at all. This is across all rows in that table, though all other data is working. An example below is taken from my python shell.
>>> c = Album(artist="Stone Roses", album_title="The Stone Roses", genre="Indie", album_logo="http://www.classicrockreview.com/Images/1989/AlbumCovers/1989_StoneRos es.jpg")
>>> c
<Album: Album object>
>>> c.id
>>> c.artist
'Stone Roses'
>>> c.pk
>>>
I was led to believe that id is automatically taken care of by Django?
Your Album object is not currently saved to the database so it has no id yet. You need to call object.save() in order to do that:
>>> c = Album(...)
>>> c.id # Object is not yet saved to the database so it has no id
None
>>> c.save() # Save object to the database
>>> c.id # Object's valid id
See the Django model instance reference docs for more detailed explanation.
In Django 1.9 I have related objects defined in models.py as follows:
from django.db import models
class Course(models.Model):
title = models.CharField(max_length=10)
class Note(models.Model):
course = models.ForeignKey(Course)
When I delete a Course, I expect all related Note s to delete via the default cascade behaviour. What I get is the following behaviour:
>>> from test1.models import Course, Note
#First, create a Course and associated Note
>>> c1 = Course()
>>> c1.save()
>>> n1 = Note(course=c1)
>>> n1.save()
>>> c1.pk
4
>>> n1.pk
4
#Next, delete the Course, and see if Note deletes too
>>> c1.delete()
(2, {'test1.Course': 1, 'test1.Note': 1})
>>> c1.pk
>>> n1.pk
1 #pk remains
>>> Note.objects.get(pk=4)
Traceback (most recent call last):
... test1.models.DoesNotExist: Note matching query does not exist.
#Finally, try deleting the Note manually
>>> n1.delete()
(0, {'test1.Note': 0})
>>> n1.pk
>>> #finally the pk is gone!
It seems that the database is correctly updated but only the Course object is updated locally, while the Note object is not (i.e. keeps its pk).
Why is this, and how can I get Note to also delete pk so that it's in sync with the db?
Extra info: My reason for needing this behaviour is that I have used statements like if note.pk: elsewhere to check if a given Note is already saved in the database. Unfortunately, this style of cascade delete renders these statements useless because pk's exist even when db entries have been deleted.
This is expected behaviour. Once Django has fetched something from the database (n1 in this case) it isn't going to know about changes to it until you fetch it again (either with Note.objects.get(pk=4) or n1.refresh_from_db().
When you call c1.delete(), Django deletes that row from the database and clears the pk on the object. It also deletes all related objects in the database that are set to cascade. However it has no way of knowing what other already-instantiated objects are referencing that object - how could it?
Here is a simpler example of the same behaviour, without foreign keys:
# Fetch a Course
>>> c1 = Course.objects.get(pk=1)
# Fetch the same course again, but save it to a different variable
>>> c2 = Course.objects.get(pk=1)
Note that you now have two instances of the same row in the database. Now:
# Delete c1
>>> c1.delete()
# c2 Hasn't changed... how could it? That would be magical.
>>> c2.pk
1
# but if you refresh c2 from the database... it isn't there
>>> c2.refresh_from_db()
DoesNotExist: Course matching query does not exist.
Reference:
>>> from haystack.query import SearchQuerySet
>>> sqs = SearchQuerySet().all()
>>> sqs[0].text # ... or whatever your document=True field is.
If you get back either u'' or None, it means that your data isn’t making it into the main field that gets searched. You need to check that the field either has a template that uses the model data, a model_attr that pulls data directly from the model or a prepare/prepare_FOO method that populates the data at index time.
I couldn't understand why my .text return nothing. Could someone explain the above notes? More specifically, I don't understand those I highlighted in bold.
On my website any user have only one group. And any user can change his group.
So it's made by
user.groups.clear()
and
user.groups.add(new_group)
But it's not efficient, because there is a two SQL query: DELETE, INSERT.
How can I change group by just UPDATE query?
User and Group are related to each other using a ManyToManyField. That means an intersection table exists relating both entities, and if you don't specify a model to map to it (using the through attribute) Django creates one for you. Looking at the sources for django.contrib.auth.models I see that's the case.
Fortunatly, you can access that intermediary model using the through attribute of the manager (in this case, User.groups.through). Then you can use it just like any regular model. Example:
>>> alice = User.objects.create_user('alice', 'alice#example.com', 'alicepw')
>>> employee = Group.objects.create(name='employee')
>>> manager = Group.objects.create(name='manager')
>>> alice.groups.add(employee)
>>> alice.groups.all()
[<Group: employee>]
>>> alice_group = User.groups.through.objects.get(user=alice)
>>> alice_group
<User_groups: User_groups object>
>>> alice_group.group = manager
>>> alice_group.save()
>>> alice.groups.all()
[<Group: manager>]
>>>
(newlines added for readability)
I generally check if obj.pk to knwo if the objects is saved. This wont work however, if you have primary_key = True set on some fields. Eg I set user = models.OneToOneField(User, primary_key=True) on my UserProfile.
What is the canonical way to find out if a Django model is saved to db?
Nowadays you can check for:
self._state.adding
This value is set by the QuerySet.iterator() for objects which are not added yet in the database. You can't use this value in the __init__() method yet, as it's set after the object is constructed.
Important Note (as of 6 May '19): If your models use UUID fields (or other method of internal ID generation, use self._state.adding as mentioned in the comments.
Actually,obj.pk is the most canonical way. Django itself often doesn't "know" if the object is saved or not. According to the django model instance reference, if there is a primary key set already, it checks onsave() calls by selecting for the id in the database before any insert.
Even if you set user = models.OneToOneField(..., primary_key=True) the .pk attribute will still point to the correct primary key (most likely user_id) and you can use it and set it as if it was the same property.
If you want to know after an object has been saved, you can catch the post_save signal. This signal is fired on model saves, and if you want you can add your own application-specific attribute to the model, for example obj.was_saved = True. I think django avoids this to keep their instances clean, but there's no real reason why you couldn't do this for yourself. Here is a minimal example:
from django.db.models.signals import post_save
from myapp.models import MyModel
def save_handler(sender, instance, **kwargs):
instance.was_saved = True
post_save.connect(save_handler, sender=MyModel)
You can alternately have this function work for all models in your app by simply connecting the signal without specifying the sender= argument. Beware though, you can create undefined behaviours if you override a property on someone else's model instance that you are importing.
Lets say obj is an instance of MyModel. Then we could use the following block of code to check if there already is an instance with that primary key in the database:
if obj.pk is None:
# Definitely doesn't exist, since there's no `pk`.
exists = False
else:
# The `pk` is set, but it doesn't guarantee exists in db.
try:
obj_from_db = MyModel.objects.get(pk=obj.pk)
exists = True
except MyModel.DoesNotExist:
exists = False
This is better than checking whether obj.pk is None, because you could do
obj = MyModel()
obj.pk = 123
then
obj.pk is None # False
This is even very likely when you don't use the autoincrement id field as the primary key but a natural one instead.
Or, as Matthew pointed out in the comments, you could do
obj.delete()
after which you still have
obj.pk is None # False
#Crast's answer was good, but I think incomplete. The code I use in my unit tests for determining if an object is in the database is as follows. Below it, I will explain why I think it is superior to checking if obj.pk is None.
My solution
from django.test import TestCase
class TestCase(TestCase):
def assertInDB(self, obj, msg=None):
"""Test for obj's presence in the database."""
fullmsg = "Object %r unexpectedly not found in the database" % obj
fullmsg += ": " + msg if msg else ""
try:
type(obj).objects.get(pk=obj.pk)
except obj.DoesNotExist:
self.fail(fullmsg)
def assertNotInDB(self, obj, msg=None):
"""Test for obj's absence from the database."""
fullmsg = "Object %r unexpectedly found in the database" % obj
fullmsg += ": " + msg if msg else ""
try:
type(obj).objects.get(pk=obj.pk)
except obj.DoesNotExist:
return
else:
self.fail(fullmsg)
Notes: Use the above code with care if you use custom managers on your models name something other than objects. (I'm sure there's a way to get Django to tell you what the default manager is.) Further, I know that /assert(Not)?InDB/ are not a PEP 8 method names, but I used the style the rest of the unittest package used.
Justification
The reason I think assertInDB(obj) is better than assertIsNotNone(obj.pk) is because of the following case. Suppose you have the following model.
from django.db import models
class Node(models.Model):
next = models.OneToOneField('self', null=True, related_name='prev')
Node models a doubly linked list: you can attach arbitrary data to each node using foreign keys and the tail is the Node obj such that obj.next is None. By default, Django adds the SQL constraint ON DELETE CASCADE to the primary key of Node. Now, suppose you have a list nodes of length n such that nodes[i].next == nodes[i + 1] for i in [0, n - 1). Suppose you call nodes[0].delete(). In my tests on Django 1.5.1 on Python 3.3, I found that nodes[i].pk is not None for i in [1, n) and only nodes[0].pk is None. However, my /assert(Not)?InDB/ methods above correctly detected that nodes[i] for i in [1, n) had indeed been deleted.