django_filters recursive filtering - django

so I have a django filter that looks like this:
class ModelFilter(FilterSet):
user_name = CharFilter(method="some_method")
boss = ModelChoiceFilter(...)
My model looks simillar to this:
class Employee(Model):
username = Charfield(...)
boss = ForeignKey("self", ''')
So an employee can be the boss of another employee. Now, this filter will return the correct queryset based on what values the user is searching for. Let's say we have three objects:
O1= Employee(usename="u1", boss=None)
O2= Employee(usename="u2", boss=O1)
O3= Employee(usename="u3", boss=O2)
If I apply the above mentioned filter on this data, and search for boss=O1 I will get the Object O2 as a result. I want to add a new boolean field in the filter, let's say "with_subordinates", that will return the whole "tree" relationship if it is true. So for instance if I would search for: boss=O1, with_subordinates=True, the resulte should be O2 and O3. Basically, with this new option, the filter should recursively show the employees, of previous employees and so forth.
Is there a way to achieve something like this?

Apart from core concepts, we will do it the pythonic way. For this, we need to get the all objects and their boss_id from the database.
boss_object_ids = [1,] # this is the input boss id
have_to_wait = True
while have_to_wait:
prev_ids = boss_object_ids
if Employee.objects.filter(boss__in=boss_object_ids).count() > 0:
boss_ids_list = Employee.objects.filter(boss__in=boss_object_ids).values_list('id', flat=True)
boss_object_ids.extend(boss_ids_list)
boss_object_ids = list(set(boss_object_ids))
if set(boss_object_ids) == set(prev_ids):
have_to_wait = False
all_subordinates = Employee.objects.filter(boss__in=boss_object_ids)
Explanation:
At first time we pass input boss_id = [1,] or some integer.
The second time it will loop and find 2,3,4,5 are the ids, hence the list will be [1,2,3,4,5].
The third time it will get 2,3,4,1,6 ids, hence the list will be [1,2,3,4,5,6].
Like wise it will filter until it gets the same list again.
That means, in this example, it will check if there is no extra number other than [1,2,3,4,5,6].
At last, we can filter using the boss_object_ids list. This is the desired all subordinates id's list.

Related

Django filter exact value in list

Good day again SO. I was hoping you can help me with some of the logic.
Based on this SO Answer, I can filter the search with a list which works perfectly. However, I wish to get an EXACT id instead of at least one matches.
models:
class Condition:
condition_name = models.CharField(....)
class Jobs:
jobs = models.CharField(...)
class JobsConditions:
account = models.ForeignKey(Account...)
job_item = models.ForeignKey(Jobs...)
condition = models.ForeignKey(Condition...)
So if I try to search for Jobs with Conditions, I do the following:
cond_array = [1,2,4,5] # Append to array based on request.
condition_obj = Condition.objects.filter(id__in=cond_array)
Then compare condition_obj to JobsConditions model. How to use this so that I will only get only the jobs with exact condition? No more no less.
I think you're wanting something like this:
Filter JobsConditions by condition__id and get the associated job_item__jobs as a list:
jobs_list = (JobsConditions.objects
.filter(condition__id__in=cond_array)
.values_list('job_item__jobs', flat=True))
Filter Jobs by that jobs_list:
jobs = Jobs.objects.filter(jobs__in=jobs_list)

Giving relations an order to sort by

Given the following Django models:
class Room(models.Model):
name = models.CharField(max_length=20)
class Beacon(models.Model):
room = models.ForeignKey(Room)
uuid = models.UUIDField(default=uuid.uuid4)
major = models.PostiveIntegerField(max_value=65536)
minor = models.PositiveIntegerField(max_value=65536)
The Beacon model is a bluetooth beacon relationship to the room.
I want to select all Rooms that match a given uuid, major, minor combination.
The catch is, that I want to order the rooms by the beacon that is nearest to me. Because of this, I need to be able to assign a value to each beacon dynamically, and then sort by it.
Is this possible with the Django ORM? In Django 1.8?
NOTE - I will know the ordering of the beacons beforehand, I will be using the order they are passed in the query string. So the first beacon (uuid, major, minor) passed should match the first room that is returned by the Room QuerySet
I am envisioning something like this, though I know this won't work:
beacon_order = [
beacon1 = 1,
beacon0 = 2,
beacon3 = 3,
]
queryset = Room.objects.annotate(beacon_order=beacon_order).\
order_by('beacon_order')
If you already know the order of the beacons, there's no need to sort within the QuerySet itself. Take an ordered list called beacon_list, which contains the beacons' primary keys in order, e.g. the item at index 0 is the closest beacon's primary key, the item at index 1 is the second closest beacon's PK, etc. Then use a list comprehension:
ordered_rooms = [Room.objects.get(pk=x) for x in beacon_list]
You don't have to use the PK either, you can use anything which identifies the given object in the database, e.g. the name field.
Looks like this works:
from django.db.models import Case, Q, When
beacons = request.query_params.getlist('beacon[]')
query = Q()
order = []
for pos, beacon in enumerate(beacons):
uuid, major, minor = beacon.split(':')
query |= Q(
beacon__uuid=uuid,
beacon__major=major,
beacon__minor=minor,
)
order.append(When(
beacon__uuid=uuid,
beacon__major=major,
beacon__minor=minor,
then=pos,
))
rooms = Room.objects.filter(query).order_by(Case(*order))

Django get count of each age

I have this model:
class User_Data(AbstractUser):
date_of_birth = models.DateField(null=True,blank=True)
city = models.CharField(max_length=255,default='',null=True,blank=True)
address = models.TextField(default='',null=True,blank=True)
gender = models.TextField(default='',null=True,blank=True)
And I need to run a django query to get the count of each age. Something like this:
Age || Count
10 || 100
11 || 50
and so on.....
Here is what I did with lambda:
usersAge = map(lambda x: calculate_age(x[0]), User_Data.objects.values_list('date_of_birth'))
users_age_data_source = [[x, usersAge.count(x)] for x in set(usersAge)]
users_age_data_source = sorted(users_age_data_source, key=itemgetter(0))
There's a few ways of doing this. I've had to do something very similar recently. This example works in Postgres.
Note: I've written the following code the way I have so that syntactically it works, and so that I can write between each step. But you can chain these together if you desire.
First we need to annotate the queryset to obtain the 'age' parameter. Since it's not stored as an integer, and can change daily, we can calculate it from the date of birth field by using the database's 'current_date' function:
ud = User_Data.objects.annotate(
age=RawSQL("""(DATE_PART('year', current_date) - DATE_PART('year', "app_userdata"."date_of_birth"))::integer""", []),
)
Note: you'll need to change the "app_userdata" part to match up with the table of your model. You can pick this out of the model's _meta, but this just depends if you want to make this portable or not. If you do, use a string .format() to replace it with what the model's _meta provides. If you don't care about that, just put the table name in there.
Now we pick the 'age' value out so that we get a ValuesQuerySet with just this field
ud = ud.values('age')
And then annotate THAT queryset with a count of age
ud = ud.annotate(
count=Count('age'),
)
At this point we have a ValuesQuerySet that has both 'age' and 'count' as fields. Order it so it comes out in a sensible way..
ud = ud.order_by('age')
And there you have it.
You must build up the queryset in this order otherwise you'll get some interesting results. i.e; you can't group all the annotates together, because the second one for count depends on the first, and as a kwargs dict has no notion of what order the kwargs were defined in, when the queryset does field/dependency checking, it will fail.
Hope this helps.
If you aren't using Postgres, the only thing you'll need to change is the RawSQL annotation to match whatever database engine it is that you're using. However that engine can get the year of a date, either from a field or from its built in "current date" function..providing you can get that out as an integer, it will work exactly the same way.

Greater than (gte) query in MongoEngine for EmbeddedDocumentListField

So this is how my models look like. Class A is having an EmbeddedDocumentListField of SlotTime.
class SlotTime(EmbeddedDocument):
# this is having minutes like 780 for 1pm.
start_time = IntField(required=True)
end_time = IntField(required=True)
class A(Document):
name = StringField(primary_key=True)
slot_hours = EmbeddedDocumentListField(SlotTime)
SlotTime has a list of objects with start and end time value.
[<SlotTime: SlotTime object>,<SlotTime: SlotTime object>]
and now I want to make a query that will return me the results that have start_time greater that to a given value.
want a somthing simliar to this query:A.objects.get(name__exact='xyz').slot_hours.filter(start_time__gte=780)
tried this but this is returning all the value. A.objects.filter(name__exact='xyz',slot_hours__start_time__gte=780)[0].slot_hours
Can someone please help me in how I can do this? Thank you!
Assuming you have the schema that you provided, you could achieve that simply with the __gte operator. See below:
A.objects(name__exact='xyz', slot_hours__start_time__gte=780)
A.objects(name__exact='xyz').filter(slot_hours__start_time__gte=780)
I don't think MongoEngine currently supports further filtering out the EmbeddedDocuments received using the .filter() query you used above. That's why accessing slot_hours returns all the SlotTime objects for that object instead of only those SlotTime objects having start_time greater than 780.
To do that, you will have to manually filter out the objects using list comprehensions.
# perform filtering and get the first 'A' object
obj = A.objects.filter(name__exact='xyz',slot_hours__start_time__gte=780)[0]
# get the relavent 'SlotTime' objects
slot_time_objs = [x for x in obj.slot_hours if x.start_time >= 780]

How to filter the items without looping

Is there a better way to query so that i dont have to loop and append the users to a list because when the length of items is very large say 10000 then it has to query 10000 times. Is there a way to avoid it?
user_list = []
items = Items.objects.all()
for item in items:
userobjects = Users.objects.filter(item=item.id)
user = userobjects[0].user
user_list.append(user)
Update
As pointed out by Sandip Agarwal, if you only want the first one, there is not easy SQL AFAIK, for performance I would do it like
user_list = []
cur = None
# If there is customized ordering,
# you could put the customization after 'item' in the following code
for user in Users.objects.filter(item__isnull=False).order_by('item').iterator():
if cur != user.item_id:
user_list.append(user)
cur = user.item_id
You haven't provided any code of models Item and User, so simply guess here:
User.objects.filter(item__isnull=False)
# or
User.objects.filter(item__in=Items.objects.all())
You can use MYSQL join to join tables and get the expected result without iterrating.
Try:
user_list = User.objects.filter(users__items__in=Items.objects.all())
However, since you're not filtering the items, you're really just looking for Users that have any Item, so you can actually do:
user_list = User.objects.filter(users__items__isnull=False)
You can achieve this by getting all Item id's then using that to filter the User's with an IN clause (in the list), with a distinct filter on the returned users. I can't test directly at the moment, but something like the following should do this for you:
item_ids = Item.objects.all().values_list('id', flat=True) # Get a list of Item id's values, since you don't need the whole Item object
user_list = User.objects.filter(items__id__in=item_ids).distinct('id') #Distinct on user id/username, filtered on those users matching an item id
user_list = list( user_list ) # Convert to a list
Note: You can use Item.objects.all() directly instead of the values_list. This may result in a bigger/slower query, try profiling it.
If this doesn't resolve the question you might need to add a clearer description of exactly what you want to see in an output. You're doing a select and taking the first value that comes out (usually this will be the first one in the database). You may be able to emulate that with the above by sorting before distinct:
user_list = User.objects.filter(items__id__in=item_ids).order_by('id').distinct('id')
If this is something you are going to do a lot, a faster solution may be to add another field to the Item record called first_user, that is set when the first user is added via the save hook.