Django - filter ManyToManyField? - python

I'm not sure the best way to describe what it is that I'm trying to do so forgive my title.
I have two models, User and Group. Group contains field, members, which is a ManyToManyField referring to User.
Given a User, I want to find all of the Groups to which that user belongs.
My idea would be to do something like this:
groups = Group.objects.filter(user in members)
Something like that. Even though I realize that this isn't right
I tried reading through this link but couldn't figure out how to apply:
http://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships
Thanks
EDIT:
Figured it out
groups = Group.objects.filter(members__username=user.username)

If you have the user and you want to have his groups then start querying from it, not the way around ;)
Here's an example:
james = User.objects.get(pk= 123)
james_groups = james.group_set.all()

The most concise way is probably
groups = user1.group_set.all()
which gives you a queryset that is iterable.

Related

Django SearchVector using icontains

I am trying to search for a list of values in multiple columns in postgres (via django). I was able to use SearchQuery and SearchVector and this works great if one of the search values matches a full word. I was hoping to use icontains so that partial strings could also be used in the search. Is this possible and if so could someone point me in the right direction. Here is an example of my approach below.
Example Data:
Superhero.objects.create(
superhero='Batman',
publisher='DC Comics',
alter_ego='Bruce Wayne',
)
Superhero.objects.create(
superhero='Hulk',
publisher='Marvel Comics',
alter_ego='Bruce Banner',
)
Django filter:
from django.contrib.postgres.search import SearchQuery, SearchVector
query = SearchQuery('man') | SearchQuery('Bruce')
vector = SearchVector('superhero', 'alter_ego', 'publisher')
queryset = queryset.annotate(search=vector).filter(search=query)
This would return the Hulk record but I am hoping I can somehow use like 'icontains' so that when searching for 'man' the Batman record would also be returned. Any help is appreciated!
You can apply icontains to the filter like:
queryset = queryset.annotate(search=vector).filter(search__icontains=query)
So SearchQuery and SearchVector are a part of Django's Full Text searching functionality and it doesnt look like you can achieve what I was wanting to do with these functions. I have taken a different approach thanks to Julian Phalip's approach here.. https://www.julienphalip.com/blog/adding-search-to-a-django-site-in-a-snap/

How can I count across several relationships in django

For a small project I have a registry of matches and results. Every match is between teams (could be a single player team), and has a winner. So I have Match and Team models, joined by a MatchTeam model. This looks like so (simplified)see below for notes
class Team(models.Model):
...
class Match(models.Model):
teams = ManyToManyField(Team, through='MatchTeam')
...
class MatchTeam(models.Model):
match = models.ForeignKey(Match, related_name='matchteams',)
team = models.ForeignKey(Team)
winner = models.NullBooleanField()
...
Now I want to do some stats on the matches, starting with looking up who is the person that beats you the most. I'm not completely sure how to do this, at least, not efficiently.
In SQL (just approximating here), I would mean something like this:
SELECT their_matchteam.id, COUNT(*) as cnt
FROM matchteam AS your_mt
JOIN matchteam AS their_mt ON your_mt.match_id = their_mt.match_id
WHERE your.matchteam.id IN <<:your teams>>
your_matchteam.winner = false
GROUP BY their_matchteam.team_id
ORDER BY cnt DESC
(this also needs a "their_mt is not your_mt" clause btw, but the concept is clear, right?)
While I have not tested this as SQL, it's just to give an insight to what I'm looking for: I want to find this result via a Django aggregation.
According to the manual I can annotate results with an aggregation, in this case a Count. Joining MatchTeams straight on MatchTeams as I'm doing in the SQL is a bit of a shortcut maybe, as there 'should' be a Match in between? At least, I wouldn't know how to translate that into Django
So maybe I need to find certain matches for my team, and then annotate them with the count of the other team? But what is 'the other team'?
Quick write-up would look like:
nemesis = Match.objects \
.filter(matchteams__in=yourteams) \
.annotate(cnt=Count('<<otherteam>>')).order_by('-cnt')[0]
If this is the right track, how should I define the Count here.
And if it's not the right track, what is?
As is, this is all about teams instead of users. This is just to keep things simple :)
An additional question might be: should I even do this with that Django ORM stuff, or am I better off just adding SQL? That has the obvious disadvantage that you're stuck with writing very generic code (is this even possible?) or fixing your DB-backend. If not needed, I'd like to avoid that.
About the model: I really want to understand what I can change about the model to make it better, but I can't really see a solution without downsides. Let me try to explain:
I want to support matches with arbitrary amount of teams, so for instance a 5-team-match. This means I have many-to-many relationship and not one that is for instance 1 match to 2 teams. If that was the case, you could denormalize and put the winners/scores in the team table. But this is not the case.
Extra data about the results of one team (e.g. their final score, their time) is by definition a property of the relation. It cannot go into the team table (as it would be per match and you can have an undefined amount of matches), and it cannot go in the match table for the same reason mutatis mutandis.
Example: I have teams A,B,C,D and E playing a match. Team A and Team B have 10 points, the rest all have 0 points. I want to save the amount of points, and that Team A and Team B are the winners of this match.
So to the comments suggesting I need a 'better' design, by all means, if you have one I would gladly see it, but if you want to support what I support, it's going to be hard.
And as a final remark: This data can be easilly retrieved in SQL, so the model seems fine to me: I'm just too much of a beginner in Django to be able to do it in Django's ORM!
Funny problem ! I think I have the answer (get the team that beats yourteams the most):
Team.objects.get( # the expected result is a team
pk=list( # filter out yourteams
filter(lambda x: x not in [ y.id for y in yourteams ],
list(
Match.objects # search matches
.filter(matchteams__in=yourteams) # in which you were involved
.filter(matchteams__winner=False) # that you loose
.annotate(cnt=Count('teams')) # and count them
.order_by('-cnt') # sort appropriately
.values_list('teams__id', flat=True) # finally get only pks
)
)
)[0] # take the first item that should be the super winner
)
I did not test it explicitly, but if does not work, I think it may be the right track.
You can do something like this
matches_won_aginst_my_team = MatchTeam.objects.filter(team=my_team, winner=False).select_related(matches)
teams_won_matches_aginst_my_team = matches_won_aginst_my_team.filter(winner=True).values_list('matchteams__team')
But as suggested you can probably model better.
I would hold two fields in the MatchModel: home_team, away_team.
Simpler and more indicative.

In django, how can I filter or exclude multiple things?

If I have this queryset:
player = basketball.objects.all()
How can I do a filter where I ask for multiple people? For example: I only want players who's names are "mike" or "charles" to show up. This doesn't seem to work for me:
player.filter(name = 'mike' , 'charles')
Does anyone know the best way to go about this?
You can use __in:
player.filter(name__in=['mike', 'charles'])
Alongside the use of __in, you can also chain multiple filters using the Q object, so objects.filter(q) where q = Q(Q(name="mike")|Q(name="phil")).
Definitely use __in though in this case.

Combining querysets obtained from a loop

Let's say I have a list of people that can be "followed".
I'd like to iterate through all the people that a certain user is following and grab posts from all of those users in the form of a queryset.
I understand that I can combine querysets by using chain or |, but I'm a bit confused when it comes to combining querysets that I might grab from looping through everyone being followed.
following = UserFollows.objects.filter(user_id = user.id)
for follow in following.iterator():
UserPost.objects.filter(user=follow.user) #what do I do with this?
How would I combine those if I cant explicitly name them to chain or '|'?
You can do something like this:
following = UserFollows.objects.filter(user__id = user.id).select_related('user')
users_ids = [follow.user.id for follow in following]
posts = UserPost.objects.filter(user__id__in=users_ids)
but look that it is quite expensive operation so it's good to add select_related() method to fetch users in one query. I think you should also consider to cache the users_ids list before get it from database.
Have you tried something like
following = UserFollows.objects.filter(user_id = user.id)
q = UserPost.objects.filter(user=following[0].user)
for follow in following[1:]:
q = q | UserPost.objects.filter(user=follow.user)

Using data from django queries in the same view

I might have missed somthing while searching through the documentation - I can't seem to find a way to use data from one query to form another query.
My query is:
sites_list = Site.objects.filter(worker=worker)
I'm trying to do something like this:
for site in sites_list:
[Insert Query Here]
Edit: I saw the awnser and im not sure how i didnt get that, maybe thats the sign im up too late coding :S
You could easily do something like this:
sites_list = Site.objects.filter(worker=worker)
for site in sites_list:
new_sites_list = Site.objects.filter(name=site.name).filter(something else)
You can also use the __in lookup type. For example, if you had an Entry model with a relation to Site, you could write:
Entry.objects.filter(site__in=Site.objects.filter(...some conditions...))
This will end up doing one query in the DB (the filter condition on sites would be turned into a subquery in the WHERE clause).

Categories