Get the intersection of a django queryset with a __in - django

So I have an array and a queryset.
some_array = [...]
queryset = queryset.filter(some_field__in=some_array)
Actually it returns every object that the "some_field" is in the array. What I want is only the objects that have all the fields from the array.
Thx

Related

How to insert a value from a dict in a django annotation using each id from queryset [duplicate]

This question already has an answer here:
Django annotate field value from external dictionary
(1 answer)
Closed last month.
I have a simple dict containing ids with values associated like: {1:True, 2:False}
My model:
class Profile(models.Model):
user_id = models.IntegerField(unique=True, blank=True, null=True)
I also have a queryset that have the same ids listed above in an atribute named user_id, but I need to add a new field to my objects on queryset with the values from my dict.
Lets suppose the field I want to add is called is_admin, so I need to create a field on the object with id 1 on my queryset with the value True.
What I tried to do is:
my_dict= {1:True, 2:False}
queryset = queryset.annotate(
is_admin=Value(my_dict.get(F("user_id")), output_field=BooleanField())
)
But what happen is that I'm receiving null on is_admin. I tried to do the code below and it works:
queryset = queryset.annotate(
is_admin=Value(my_dict.get(1), output_field=BooleanField())
)
So I think is something wrong with my use of F expression. I will appreciate any help.
I guess it is not ok to mix non-trivial python code into the query expression, beacause it needs to be translated to raw SQL.
I suggest setting this property after you get the data from DB. Something like this:
results = list(queryset)
for r in results:
r.is_admin = my_dict.get(r.id, False)
Other alternatives, if you want to use this in multiple places:
create new #property def is_admin(self): return my_dict.get(self.id, False)
add it as a new field for your model
The problem is that the expression F is used to modify strings while your dictionary keys are integer values. Modify the my_dict.get() value by casting it to an integer using int()
>>> newString = f"{user_id}"
>>> newString
'2'
>>> print(my_dict.get(newString))
None
>>> newInt = int(newString)
>>> print(my_dict.get(newInt))
False

Join annotations in Django without raw SQL

I have a model that has arbitrary key/value pairs (attributes) associated with it. I'd like to have the option of sorting by those dynamic attributes. Here's what I came up with:
class Item(models.Model):
pass
class Attribute(models.Model):
item = models.ForeignKey(Item, related_name='attributes')
key = models.CharField()
value = models.CharField()
def get_sorted_items():
return Item.objects.all().annotate(
first=models.select_attribute('first'),
second=models.select_attribute('second'),
).order_by('first', 'second')
def select_attribute(attribute):
return expressions.RawSQL("""
select app_attribute.value from app_attribute
where app_attribute.item_id = app_item.id
and app_attribute.key = %s""", (attribute,))
This works, but it has a bit of raw SQL in it, so it makes my co-workers wary. Is it possible to do this without raw SQL? Can I make use of Django's ORM to simplify this?
I would expect something like this to work, but it doesn't:
def get_sorted_items():
return Item.objects.all().annotate(
first=Attribute.objects.filter(key='first').values('value'),
second=Attribute.objects.filter(key='second').values('value'),
).order_by('first', 'second')
Approach 1
Using Djagno 1.8+ Conditional Expressions
(see also Query Expressions)
items = Item.objects.all().annotate(
first=models.Case(models.When(attribute__key='first', then=models.F('attribute__value')), default=models.Value('')),
second=models.Case(models.When(attribute__key='second', then=models.F('attribute__value')), default=models.Value(''))
).distinct()
for item in items:
print item.first, item.second
Approach 2
Using prefetch_related with custom models.Prefetch object
keys = ['first', 'second']
items = Item.objects.all().prefetch_related(
models.Prefetch('attributes',
queryset=Attribute.objects.filter(key__in=keys),
to_attr='prefetched_attrs'),
)
This way every item from the queryset will contain a list under the .prefetched_attrs attribute.
This list will contains all filtered-item-related attributes.
Now, because you want to get the attribute.value, you can implement something like this:
class Item(models.Model):
#...
def get_attribute(self, key, default=None):
try:
return next((attr.value for attr in self.prefetched_attrs if attr.key == key), default)
except AttributeError:
raise AttributeError('You didnt prefetch any attributes')
#and the usage will be:
for item in items:
print item.get_attribute('first'), item.get_attribute('second')
Some notes about the differences in using both approaches.
you have a one idea better control over the filtering process using the approach with the custom Prefetch object. The conditional-expressions approach is one idea harder to be optimized IMHO.
with prefetch_related you get the whole attribute object, not just the value you are interested in.
Django executes prefetch_related after the queryset is being evaluated, which means a second query is being executed for each clause in the prefetch_related call. On one way this can be good, because it this keeps the main queryset untouched from the filters and thus not additional clauses like .distinct() are needed.
prefetch_related always put the returned objects into a list, its not very convenient to use when you have prefetchs returning 1 element per object. So additional model methods are required in order to use with pleasure.

dynamic values or keyword args to values_list() and values()?

How to pass dynamic type column names to values_list() and values() method in django?
To send a column names dynamically to values_list():
You can either send as a list or tuple or dictionary as a keyword arguments. But the problem is values_list() method does not accept arguments as list or tuple or dictionary.
So the solution is, have those dynamic column names in either list or tuple and receive that as a keyword argument.
i.e, list = ['id', 'first_name', 'gender'] if this is your list of column names needs to pass to values_list().
querySet = User.objects.filter(username = 'mahajan535').values_list(*list)
If you have dictionary to store dynamic column names then
querySet = User.objects.filter(username = 'mahajan535').values_list(**dict)
This solves you problem.
For more info: https://docs.python.org/2/tutorial/controlflow.html#unpacking-argument-lists

Django - sorting object based on user defined order in template

I want the user to be able to order a list of objects in a table using javascript. Then, in a django function I would like to sort those object based on the same ordering, not on an attribute.
Is it possible? I was thinking about passing a list of pk from the template to the view and then ordering the objects according to this list, but I have not found a way to do it yet.
I don't think this is possible with queryset. Try following:
pk_list = [2, 1, 3, 4]
pk2obj = {obj.pk: obj for obj in Model.objects.all()}
objects_ordered = [pk2obj[pk] for pk in pk_list]
pkg2obj is mapping between pk and model instance object. To make a dictionary I used dictionary comprehension.
If you want to omit deleted objects:
objects_ordered = [pk2obj[pk] for pk in pk_list if pk in pk2obj]
Else if you want to replace deleted objects with default value (None in following code):
objects_ordered = [pk2obj.get(pk, None) for pk in pk_list]
I've had to solve this exact problem before.
If you want the user to be able to reorder them into a user-defined order, you can easily define a field to store this order.
As you say, initially, you could serve them in order according to id or an upload_date DateTimeField. But you could also have an PositiveIntegerField in the model, named position or order, to represent the user-defined order.
class MediaItem(models.Model):
user = models.ForeignKey(User)
upload_date = models.DateTimeField(auto_now_add = True)
position = models.PositiveIntegerField()
Whenever a user changes the order on the frontend, the JS can send the new order as an array of objects (ie. new_order = [{"pk":3, "position":1}, {"pk":1, "position":2}, {"pk":2, "position":3}]). The view can look up each instance by pk, and change the position:
for obj in new_order:
media_item = MediaItem.objects.get(pk=obj['pk'])
media_item.position = obj['position']
media_item.save()
Then always query using
objects_ordered.objects.order_by('position')
That's how we managed to do it. If you have more specific questions regarding this approach, feel free to ask in the comments.
Edit:
If the same object can be a member of many different groups or lists, and you want to store the position of the membership within that list, you can achieve this using a through model. A through model is useful when you need to store data that relates to the relationship between two objects that are related. In addition to the MediaItem class shown above, this is what your other models would look like:
class Album(models.Model):
media_items = models.ManyToManyField(MediaItem,
related_name = 'album_media_items',
through = 'Membership')
class Membership(models.Model):
album = models.ForeignKey(Album,
related_name = 'album')
media_item = models.ForeignKey(MediaItem,
related_name = 'media_item')
date = models.DateTimeField(auto_now_add = True)
position = models.PositiveIntegerField()
Then, you could query the Membership instances, instead of the MediaItem instances.
# get id of list, or album...
alb = Album.objects.get(pk=id_of_album)
media_items = Membership.objects.filter(album=alb).order_by('position')
for item in media_items:
# return the items, or do whatever...
# keep in mind they are now in the user-defined order
You can do this:
pk_list = [1,5,3,9]
foo = Foo.objects.filter(id__in=pk_list)
#Order your QuerySet in base of your pk_list using Lambda
order_foo = sorted(foo, key = lambda:x , pk_list.index(x.pk))

Django values_list with choices field

The values_list in filtering object, really helps me a lot in providing solution within django view.
My code is like the following and this one works:
values_list_ac = realdata.objects.filter(product = '1').values_list('company', 'brand', 'created_by__username')
while username is the field exists in different model outside the current realdata model.
But the following code doesn't work, for I want to show the value of ac_type, which based on choices field within the same realdata model. (I try to solve it by using the same solution which work in template):
values_list_ac = realdata.objects.filter(product = '1').values_list('company', 'brand', 'created_by__username', 'get_ac_type_display')
Is there a solution other than get_ac_type_display to show the field value?
I really appreciate for some shed of light.
Edit:
This my model:
class realdata(models.Model):
company = models.CharField(max_length=60, verbose_name="Company")
brand = models.CharField(_('brand'), max_length=60)
model = models.CharField(max_length=60)
type_choices = (
(u'1', u'Inverter'),
(u'2', u'Non-Inverter'),
)
ac_type = models.CharField(max_length=60, verbose_name="Type", choices=type_choices)
created_by = models.ForeignKey(User)
Many Thanks!
The values_list function will just get the values stored in the database. When defining choices on your model's field, it will store the first value of the tuple, hence this will be what you'll retrieve.
This means that you have to look at the choices tuple to determine the display value for the item. The purpose of the get_foo_display is to give you this human-readable value, but it needs a model instance to work on.
So, a solution to resolving this yourself would be to inspect the choices, and convert the data accordingly. The following should be able to do this:
result = []
for p in realdata.objects.filter(product='1').values_list(
'company', 'brand', 'created_by__username', 'ac_type'):
choice = {k: v for k, v in realdata.type_choices}[p[-1]]
result.append(list(p[:-1]) + [choice])
The result variable will contain the converted list. The new variable is needed because the values_list function will return a list of tuples; the latter being unmutable. Also, take care to have the value you'll want to resolve as the last item in your values_list call, or adapt the above to match.