Django - How to remove F() from a field? - django

after doing a query with F() (see http://docs.djangoproject.com/en/dev/topics/db/queries/#query-expressions), I save the object, but then, I need to save it again. I want to remove F() from the field. If I don't, F() gets called again.
For example
rank.ammountMatchesRanked = F('ammountMatchesRanked') + 1
rank.save() # does ammountMatchesRanked = ammountMatchesRanked + 1
... # Manipulating more rank fields (can't manipulate before)
rank.save() # does ammountMatchesRanked++ again (undesired)
Any idea on how can I clear reference fields? I have searched the documentation but I didn't find it.
Thanks in advance

Why are you using F() here at all? F is really for use within queries, where you want to just get those objects who have a certain property with some relation to another property in the same model. Once you've got the object, there's no need to use it - you can just do it in standard Python:
rank.ammountMatchesRanked += 1
rank.save()
Edited after comment No, you have misunderstood what lazy loading is. It applies to instances (ie database rows) within a queryset, not fields (ie columns) within an instance. So once you have accessed an instance at all, Django by default will load all its fields (except those you have marked with defer()), so the above will not result in an extra query.
The documentation for using F() in updates which you linked to explains that this is only if you're not doing anything else with the object. You are, so this is not an optimisation.

i am not that good in django, but what about doing this:
rank.ammountMatchesRanked = F('ammountMatchesRanked');
just before the second call?

Related

Get part of django model depending on a variable

My code
PriceListItem.objects.get(id=tarif_id).price_eur
In my settings.py
CURRENCY='eur'
My Question:
I would like to pick the different info depending on the CURRENCY variable in settings.py
Example:
PriceListItem.objects.get(id=tarif_id).price_+settings.CURRENCY
Is it possible?
Sure. This has nothing to do with Django actually. You can reach the instance's attribute through pure Python:
getattr(PriceListItem.objects.get(id=tarif_id), 'price_'+settings.CURRENCY)
Note it might be a better idea to have a method on the model which accepts the currency as a parameter and returns the correct piece of data (through the line I wrote above, for example).
I think this should work
item = PriceListItem.objects.get(id=tarif_id)
value = getattr(item, price_+settings.CURRENCY)
In case you are only interested in that specific column, you can make the query more efficient with .values_list:
my_price = PriceListItem.objects.values_list_(
'price_{}'.format(settings.CURRENCY),
flat=True
).get(id=tarif_id)
This will only fetch that specific column from the database, which can be a (a bit) faster than first fetching the entire row into memory, and then discard all the rest later.
Here my_price is thus not a PriceListItem object, but the value that is stored for the specific price_cur column.
It will thus result in a query that looks like:
SELECT pricelistitem.price_cur
FROM pricelistitem
WHERE id=tarif_id

Detect duplicate inserts when adding many-to-many relation

Let's assume there are two models, A and B:
class A(models.Model):
name = models.CharField(max_length=100)
class B(models.Model):
children = models.ManyToManyField(A)
I'm using b.children.add() method to add instance of A to b:
a = A.objects.get(pk=SOMETHING)
b.children.add(a)
As far as I know, Django by default doesn't allow duplicate many-to-many relationship. So I cannot add same instance of A more than once.
But the problem is here, I fetch instances of A with another query, then loop around them and add them one by one. How can I detect a duplicate relation? Does add() method return something useful?
A look at the source code reveals that Django first checks to see if there are any entries that already exist in the database, and then only adds the new ones. It doesn't return any information to the caller, though.
It's not clear if you actually need to detect duplicates, or if you just want to make sure that they're not being added to the database? If it's the latter then everything's fine. If it's the former, there's no way around hitting the database. If you're really concerned about performance you could always perform the check and update the through table yourself (i.e. re-implement add()).

Will Django use previously-evaluated results when applying additional filters to a query set?

Let's say I need to do some work both on a set of model objects, as well as a subset of the first set:
things = Thing.objects.filter(active=True)
for thing in things: # (1)
pass # do something with each `thing`
special_things = things.filter(special=True)
for thing in special_things: # (2)
pass # do something more with these things
My understanding is that at point (1) marked in the code above, an actual SQL query something like SELECT * FROM things_table WHERE active=1 will get executed against the database. The QuerySet documentation also says:
When a QuerySet is evaluated, it typically caches its results.
Now my question is, what happens at point (2) in the example Python code above?
Will Django execute a second SQL query, something like SELECT * FROM things_table WHERE active=1 AND special=1?
Or, will it use the cached result from earlier, automatically doing for me behind the scenes something like the more optimal filter(lambda d: d.special == True, things), i.e. avoiding a needless second trip to the database?
Either way, is the current behavior guaranteed (by documentation or something) or should I not rely on it? For example, it is not only a point of optimization, but could also make a possible logic difference if the database table is modified by another thread/process between the two potential queries.
It will execute a second SQL query. filter creates a new queryset, which doesn't copy the results cache.
As for guarantees - well, the docs specify that filter returns a new queryset object. I think you can be confident that that new queryset won't have cached results yet. As further support, the "when are querysets evaluated" docs suggest using .all() to get a new queryset if you want to pick up possibly changed results:
If the data in the database might have changed since a QuerySet was
evaluated, you can get updated results for the same query by calling
all() on a previously evaluated QuerySet.

Is there any way around saving models that reference each other twice?

My issue is when saving new models that need to reference each other, not just using a related_name lookup, such as this:
class Many:
owner = models.ForeignKey('One')
class One:
current = models.OneToOneField('Many')
By default, these have null=False and, please correct me if I'm wrong, using these are impossible until I change one of the relationships:
current = models.OneToOneField('Many', null=True)
The reason is because you can't assign a model to a relationship unless its already saved. Otherwise resulting in ValueError: 'Cannot assign "<...>": "..." instance isn't saved in the database.'.
But now when I create a pair of these objects I need to save twice:
many = Many()
one = One()
one.save()
many.owner = one
many.save()
one.current = many
one.save()
Is this the right way to do it, or is there another way around saving twice?
There is no way around it, you need to save one of the objects twice anyway.
This is because, at the database level, you need to save an object to get its ID. There is no way to tell a sql database "save those 2 objects and assign the ids to those fields on the other object". So if you were to do it manually, you would INSERT the first object with NULL for the FK, get its ID back, INSERT the second object with the ID of the first one, get its ID back, then UPDATE the first object to set the FK.
You would encapsulate the whole thing in a transaction.
So what you're doing with the ORM is the closest you can get. You may want to add the following on top of that:
1) Use a transaction for the changes, like this:
from django.db import transaction
with transaction.atomic():
many, one = Many(), One()
one.save()
many.owner = one
many.save()
one.current = many
one.save(update_fields=['current']) # slight optimization here
2) Now this is encapsulated in a transaction, you would want to remove the null=True. But you cannot, as those are, unfortunately, checked immediately.
[edit: it appears Oracle might support deferring the NOT NULL check, so if you're using Oracle you can try dropping the null=True and it should work.]
You'll probably want to check how your code reacts if at a later point, when reading the db, if for some reason (manual editing, bugged insert somewhere, ...) one.current.owner != one.

In django, can one use F() objects to see if a constant contains strings from a field?

All,
I'm trying to basically do keyword notifications, so whenever an object with a name is created, anyone who wants to be notified of any of the words in this name will be.
e.g.
Records:
keyword: Hello
keyword: World
New Name: "Hello World"
Returns both records
I've created a query that correctly works for this in sqlite, and I know how to translate it across databases.
SELECT * FROM table t
WHERE "a constant string" LIKE "%" || t.field || "%";
I've determined that within django, one can use F() objects to compare one field to another field, like so:
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Now anyone know how to replace the first field with a constant string? Like so:
Entry.objects.filter("constant string"__icontains=F('n_pingbacks'))
Or am I going about this backwards?
It doesn't look particularly pretty but it can be done all with standard filters.
from django.db.models import ExpressionWrapper, CharField, Value
Entry.objects.annotate(
my_const=ExpressionWrapper(Value("a constant string"),
output_field=CharField())
).filter(my_const__contains=F('n_pingbacks'))
You can do this by providing a dict of arguments, e.g.:
Entry.objects.filter(**{("%s__icontains" % constant_string):F('n_pingbacks')})
Try using '.extra' to select your const as field, than using myconst__contains, like:
queryset.extra(select={'myconst': "'this superstring is myconst value'"}).filter(myconst__contains=F('myfield'))
Do not forget to put constant value in apostrophes inside double qoutation marks.
But, will somebody help me put it into Q object? =)
UPD:
Suddenly, it fails because of the following issue:
https://code.djangoproject.com/ticket/13363
Maybe, they will fix it.
UPD: You can filter by fields that added with '.annotate', but I don't know, how to put constant here instead of aggregation. Maybe, with creation of custom aggreation function, like here:
http://coder.cl/2011/09/custom-aggregates-on-django/
UPD: I made custom aggregator, this logic seems to be correct, because the query I got from queryset is quite similar to I wanted it to be, but, unfortunately, there is another issue: 16731 (sorry not providing full url, not enough rep, see another ticket above).
UPD(last): I have managed to do this using monkeypatching of the following:
django.db.models.sql.Query.query_terms
django.db.models.fields.Field.get_prep_lookup
django.db.models.fields.Field.get_db_prep_lookup
django.db.models.sql.where.WhereNode.make_atom
Just defined custom lookup 'starts', which has reverse logic of 'startswith'
You should not try to fight with django ORM. It covers the most often things but your case is not the one. Just use extra to get what you need (or even raw).