I have two functions
def xyz(obj):
obj_queryset = Sample.objects.filter(id=obj.id)
callfunction(obj_queryset)
def callfunction(obj_queryset):
for obj in obj_queryset:
obj.start_date = datetime.date.today()
obj.end_date = datetime.date.today()
obj.save()
I need it as a queryset for certain reasons because i want to update multiple objects and I am also doing few calculations before updating which I am not posting here.
Is there any way to turn obj into a queryset without doing a database query.
callfunction does not use anything specific from queryset. It treats its argument as a sequence a.k.a. iterable. Practically speaking it means that anything you can use in a for loop can be passed to the function.
In order to process a single object by this function you can pass a sequence with that object to your function and it can be any sequence that is iterable (not necessarily queryset), for example it can be a list:
callfunction([obj])
This is absolutely valid and it a usual python idiom.
Related
I have below model in my Django app.
class Revenue(models.Model):
from_a = models.IntegerField()
from_b = models.IntegerField()
def get_total(self):
return self.from_a + self.from_b
Now I am retrieving data using Revenue.objects.filter(from_a__gt = 10).values('from_a', 'from_b').
From the above queryset I am getting values, now I want to call get_total function on objects.
I didn't found a way to call that function.
Is there a way to retrieve the data only I needed using values and also can call member_functions of that objects?
Revenue.objects.filter(from_a__gt = 10) should not be the solution if I have 100s of columns for my model.
Instead of doing this on model level you can use F expression with your query:
from django.db.models import F
Revenue.objects.filter(from_a__gt = 10).annotate(
get_total=F('from_a') + F('from_b')
).values('from_a', 'from_b', 'get_total')
From the above queryset I am getting values, now I want to call get_total function on objects. I didn't found a way to call that function.
Well you obtain a QuerySet (that is at that point not evaluated), so a collection of Revenues. You can not directly call the function on that collection. But you can iterate through the queryset, and call the function on the inidvidual objects. We can for example make a list with:
[r.get_total() for r in Revenue.objects.filter(from_a__gt = 10)]
Is there a way to retrieve the data only I needed using values and also can call member_functions of that objects?
Yes, you can use the .only(..) element on the query, to restrict the number of columns that are loaded:
[r.get_total() for r in Revenue.objects.filter(from_a__gt = 10).only('from_a', 'from_b')]
This will construct Revenue objects, but we will only load the specified columns. In that case we will load only from_a and from_b, and if you later need other fields, these will be loaded with extra queries.
In case however the logic in the member_functions is easy, you better use annotations: these are then processed in the database, and thus allow filtering. This is however not always possible: Python allows to calculate very complicated things that would result in a gigantic equivalent SQL expression. Furthermore most databases do not allow to contact webservices and file systems, so some functions are fundamentally impossible to translate in an annotation.
Depends in what you want to do... if you want get_total shows as field, you can make this method one property method, he will be calculated field, so its not persist in data base
Property Method
#property
def get_total(self):
return self.from_a + self.from_b
Revenue.objects.filter(from_a__gt = 10).values('from_a', 'from_b', 'get_total')
https://docs.djangoproject.com/en/2.0/topics/db/models/#model-methods
You can call it like regular class method, so when you get the model instance you can call it
revenues = Revenue.objects.filter(from_a__gt = 10).values('from_a', 'from_b')
for revenue in revenues:
print(revenue.get_total)
# OR
[r.get_total for r in Revenue.objects.filter(from_a__gt = 10)] # Like Wilem Van said
Static Method
Using it as static method (this way isnt the best fit for your need but is nice to know), you will bind this method with the class, so you dont need one object instance to manage it, just the class reference
class Revenue(...):
...
#staticmethod
def get_total(valueA, valueB):
return valueA + valueB
To call it is like that
Revenue.get_total(5, 10)
https://www.programiz.com/python-programming/methods/built-in/staticmethod
Obs.: Weird, didnt got one good referente to staticmethod at django docs
Consider the following django code
phonenumbers = othermodel.objects.filter( company=mymodel.company,
date_overwritten__isnull=True
).values_list('id', flat=True)
This is returning another QuerySet object instead of a python list, the first object in the queryset returned is a {valuesListQuerySet} which is an empty list and this is correct because my whole model/table othermodel is currently empty
What am i missing? and how do i get just the {valuesListQuerySet} as a normal python list even if its empty
As the django doc says
If you don’t pass any values to values_list(), it will return all the
fields in the model, in the order they were declared.
Note that this method returns a ValuesListQuerySet. This class behaves
like a list. Most of the time this is enough, but if you require an
actual Python list object, you can simply call list() on it, which
will evaluate the queryset.
So you could call:
list(phonenumbers)
I need to update a couple of objects using update(), but not the save() method.
Is it possible to cast a model to a queryset to do that ?
Otherwise I tried something like:
objects = Article.objects.all()
ctype = ContentType.objects.get_for_model(Article)
objects.update( stats=process_data(F('id'), ctype) )
process_data gets other objects relatif to Article and after a calculation returns a float:
def process_data(object_id, content_type):
counts = Counter.objects.get(content_type=content_type, object_pk=object_id)
...
return float(calculation_based_on_counts)
Unfortunately this is not possible. I get a MultipleObjectsReturned exception:
counter.models.MultipleObjectsReturned: get() returned more than one Counter -- it returned 15! Lookup parameters were {'object_pk': , 'content_type': }
Thanks for any help !
The F class does not return a value, as your code expects it to. F objects are nodes in an expression.
F expressions only allow a certain subset of operations, mainly boolean logic and arithmetic: not arbitrary functions. If you're interested in how this works under the hood, look at django/db/models/expressions.py. It's not as well documented as it could be.
Without knowing what your calculation is, it's not possible to say whether it's possible with an F. If you were, for example, updating the sum of some related objects you could do it by combining aggregate with an update using an F expression.
when got a QuerySet by using filter and it's easy to use the following code to do the change and save operation:
qs = SomeModel.objects.filter(owner_id=123)
# suppose qs has 1 or many elements
last_login_time = qs[0].last_login_time
qs[0].last_login_time = datetime.now() # I expect it can assign the new value, but it won't
assertEquals(qs[0].last_login_time, last_login_time) # YES, it doesn't change
qs[0].save() #So it won't update the old record
And after figuring this out, the following code will be used instead and it works:
qs = SomeModel.objects.filter(owner_id=123)
# suppose qs has 1 or many elements
obj = qs[0]
last_login_time = obj.last_login_time
obj.last_login_time = datetime.now() # I expect it can assign the new value, but it will
assertNotEquals(obj.last_login_time, last_login_time) # YES, it does change
obj.save() #So it will update the old record as expected
And I have met some of my friends/colleagues use the first approach to do the record updating. And IMO, it's natural and prone to use. (when you type qs[0] and type obj , they have the same type)
After reading the code(db.models.query), it can be figured out why.(when you subscript the QuerySet it will use the qs = self._clone() and assigning a value won't change at all)
Possible solutions:
make the assigning work for the
subscripting QuerySet
announce the
above first approach is wrong and
let the users know it
So I want to ask:
Is my question a real issue for django?(I'm wondering why django developer not make it work as expected)
What's your suggestion about this issue? And what's your preferred way for such an issue?
Use update for updating fields in a queryset.
I'm not really sure what you're asking here. Are you saying this is a bug? I don't think so, it's clearly defined behaviour: the queryset is lazy, but is evaluated when you iterate or slice it. Each time you do slice it, you get a new object. This is the logical consequence of the fact that slicing by itself doesn't cause the non-sliced queryset to be evaluated - if the result isn't already cached, slicing will perform a single database call with a LIMIT 1 to only get one result. Otherwise, you're left with extremely undesirable side-effects.
Now, if you think this could be better explained in the docs, you're welcome - and encouraged - to submit a bug with a patch that explains it better.
I have the following:
answers = Answer.objects.filter(id__in=[answer.id for answer in answer_set.answers.all()])
then later:
for i in range(len(answers)):
# iterate through all existing QuestionAnswer objects
for existing_question_answer in existing_question_answers:
# if an answer is already associated, remove it from the
# list of answers to save
if answers[i].id == existing_question_answer.answer.id:
answers.remove(answers[i]) # doesn't work
existing_question_answers.remove(existing_question_answer)
I get an error:
'QuerySet' object has no attribute 'remove'
I've tried all sorts to convert the QuerySet to a standard set or list. Nothing works.
How can I remove an item from the QuerySet so it doesn't delete it from the database, and doesn't return a new QuerySet (since it's in a loop that won't work)?
Why not just call list() on the Queryset?
answers_list = list(answers)
This will also evaluate the QuerySet/run the query. You can then remove/add from that list.
You could do this:
import itertools
ids = set(existing_answer.answer.id for existing_answer in existing_question_answers)
answers = itertools.ifilter(lambda x: x.id not in ids, answers)
Read when QuerySets are evaluated and note that it is not good to load the whole result into memory (e.g. via list()).
Reference: itertools.ifilter
Update with regard to the comment:
There are various ways to do this. One (which is probably not the best one in terms of memory and time) is to do exactly the same :
answer_ids = set(answer.id for answer in answers)
existing_question_answers = filter(lambda x: x.answer.id not in answers_id, existing_question_answers)
It is a little hard to follow what you are really trying to do. Your first statement looks like you may be fetching the same exact QuerySet of Answer objects twice. First via answer_set.answers.all() and then again via .filter(id__in=...). Double check in the shell and see if this will give you the list of answers you are looking for:
answers = answer_set.answers.all()
Once you have that cleaned up so it is a little easier for you (and others working on the code) to read you might want to look into .exclude() and the __in field lookup.
existing_question_answers = QuestionAnswer.objects.filter(...)
new_answers = answers.exclude(question_answer__in=existing_question_answers)
The above lookup might not sync up with your model definitions but it will probably get you close enough to finish the job yourself.
If you still need to get a list of id values then you want to play with .values_list(). In your case you will probably want to add the optional flat=True.
answers.values_list('id', flat=True)
By the use of slice operator with step parameter which would cause evaluation of the queryset and create a list.
list_of_answers = answers[::1]
or initially you could have done:
answers = Answer.objects.filter(id__in=[answer.id for answer in
answer_set.answers.all()])[::1]
You can directly convert using the list keyword.
For example:
obj=emp.objects.all()
list1=list(obj)
Using the above code you can directly convert a query set result into a list.
Here list is keyword and obj is result of query set and list1 is variable in that variable we are storing the converted result which in list.
Try this values_list('column_name', flat=True).
answers = Answer.objects.filter(id__in=[answer.id for answer in answer_set.answers.all()]).values_list('column_name', flat=True)
It will return you a list with specified column values
Use python list() function
list(). Force evaluation of a QuerySet by calling list() on it. For
example:
answers = list(answer_set.answers.all())
Why not just call
.values('reqColumn1','reqColumn2') or .values_list('reqColumn1','reqColumn2') on the queryset?
answers_list = models.objects.values('reqColumn1','reqColumn2')
result = [{'reqColumn1':value1,'reqColumn2':value2}]
OR
answers_list = models.objects.values_list('reqColumn1','reqColumn2')
result = [(value1,value2)]
You can able to do all the operation on this QuerySet, which you do for list .
def querySet_to_list(qs):
"""
this will return python list<dict>
"""
return [dict(q) for q in qs]
def get_answer_by_something(request):
ss = Answer.objects.filter(something).values()
querySet_to_list(ss) # python list return.(json-able)
this code convert django queryset to python list
instead of remove() you can use exclude() function to remove an object from the queryset.
it's syntax is similar to filter()
eg : -
qs = qs.exclude(id= 1)
in above code it removes all objects from qs with id '1'
additional info :-
filter() used to select specific objects but exclude() used to remove