Refresh from DB keeps OneToOneField Relation - django

I have a simple model relation:
class Foo(models.Model):
bar = models.OneToOneField(Bar)
Say I do the following:
>>> bar = Bar.objects.create()
>>> foo = Foo.objects.create(bar=bar)
>>> Foo.objects.all().delete()
>>> bar.foo is None
False
This is expected because bar is still referencing the foo object. But now when I try to get a fresh copy of bar from the DB, i.e. without the related foo, I tried:
>>> bar.refresh_from_db()
>>> bar.foo is None
False
Why does foo not come back as None? I see that in the docs it says that only fields of the model are reloaded from the database when using refresh_from_db(). Does foo not count as a field of bar in this case?

Which Django version are you using?
Earlier versions of Django did not clear cache when relationship objects did not change it's id's. There is their ticket https://code.djangoproject.com/ticket/29076.
It's already fixed in the new versions of Django.

Related

Django slots + foreign key save silently fails

I have found that when Django2.0 model has ForeignKey and mixin with slots it's .save() method doesn't work. While it's quite specific case, it is still kinda surprising, because there is no any Exception, data is just not saved. Here is an example:
from django.db import models
class FooSlots:
__slots__ = ["bar", "value"]
class Bar(models.Model):
pass
class FooSloted(models.Model, FooSlots):
value = models.FloatField(default=0.42)
bar = models.ForeignKey(Bar,
on_delete=models.CASCADE,
related_name="foo_sloted"
)
def check_sanity(source, bar, value=0.5):
instance = source.objects.create(bar=bar)
instance.value = value
instance.save()
instance = source.objects.get(pk=instance.pk)
assert instance.value == value # Must be true!
So
check_sanity(FooSloted, Bar.objects.first())
will raise assertion error, because data won't be saved, but there is no exceptions from Django itself. Even more confusing that in case bar is not a ForeignKey, but e.g. a CharField, everything is ok. Also when slots are not specified, there is no such problem too.
Is there any explanation for such behavior?
PS. To make this example less fictional: I have several "Foo"-like models with keys to different "Bar" which are populated in similar way elsewhere. FooSlots is used to enforce same interface and to treat given data for different Foo in a same way.

FactoryBoy "create" strategy doesn't seem to save django model

Probably a silly question, but I've been banging my head against a wall for a little while now.
I decided to try factory-boy library to simplify my tests and defined a factory:
from . import models
import factory
class QualtricsSurveyCacheFactory(factory.Factory):
class Meta:
model = models.QualtricsSurveyCache
survey_id = "SR_1234"
qualtrics_username = "bla#blah.bla#bla"
survey_name = "fake"
However, when I do QualtricsSurveyCacheFactory.create() it returns model with id = None
>>> survey = QualtricsSurveyCacheFactory()
>>> print survey.id
None
I can .save() model after creation, but just curious why it doesn't do it automatically.
You weren't using the correct base class for Django models. Inherit instead from:
class QualtricsSurveyCacheFactory(factory.DjangoModelFactory):
...
Then, QualtricsSurveyCacheFactory() will return a saved instance with a primary key. Use QualtricsSurveyCacheFactory.build() if you want an unsaved instance.

Django cascade delete doesn't remove pk of referenced model instance

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.

How can I change user group without delete record

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)

Django ForeignKey Instance vs Raw ID

Is there a way to not have to pass in a model instance for a foreign key when create a new model? Let's say I have the following models:
class Foo(models.Model):
description = models.CharField(max_length=100)
class Meta:
db_table = u'foo'
class Bar(models.Model):
info = models.CharField(max_length=100)
foo = models.ForeignKey('Foo')
class Meta:
db_table = u'bar'
The later a post request comes in to a view - I know the the id of a foo record and just want to insert a record into the bar table.
if I do:
new_bar = Bar(info="something important", foo=foo_id)
new_bar.save()
I get a ValueError saying "Cannot assign "546456487466L": "Bar.foo" just be a "Foo" instance.
So, I get it... it wants me to have an actual instance of the Foo model. I understand that I can just do a get on Foo and then pass it in. But, there seems like there must be a way to override this functionality. I have done some googling and reading the docs, and raw_id_fields in admin seems to be the basic idea. (which is to say, allow a raw id here). But, don't see this option on the ForeignKey field.
It seems very inefficient to have to make a round trip to the database to get an object to get the id (which I already have). I understand that doing the round trip validates that the id exists in the database. But, hey... that's why I'm using a RDBMS and have foreign keys in the first place.
Thanks
new_bar = Bar(info="something important", foo_id=12345)
new_bar.save()
You can also get foreign key values directly. Some kind of optimization.