django conjunctive filter __in query - python

Consider an array of Tags, T.
Each PhotoSet has a many-to-many relationship to Tags.
We also have a filter, F (consisting of a set of Tags), and we want to return all PhotoSets who have ALL the tags contained in F.
i.e,. if F = ['green', 'dogs', 'cats'], we want every PhotoSet instance that has all the tags in F.
Naturally
PhotoSet.objects.filter(tags__in=F)
Does not do the trick, since it returns every PhotoSet contain any member of F.
I see it's possible to use similar things using "Q" expressions, but that only seemed for a finite amount of conjunctive parameters. Is this something that can be done using a list comprehension??
Thanks in advance!
EDIT -- SOLUTION:
I found the solution using an obvious way. Simply chaining filters...
results = PhotoSets.objects
for f in F:
results = results.filter(tags__in=[f])
results = results.all()
Was staring me in the face the whole time!

Little quick and dirty, but it'll do the trick:
query = None
for tag in F:
if query is None:
query = Q(tags=tag)
else:
query &= Q(tags=tag)
PhotoSet.objects.filter(query)

Related

Django QuerySet Lookup Fails When Testing for Value "Local"

I have a QuerySet which returns a list of user-defined tags. In some cases, I'd like to exclude any of the tags that start with the word "Local", but this seems to be causing me problems.
The following examples work when I'm testing for other values (like HVAC below):
queryset = queryset.exclude(tags__tag__tag_name__icontains = 'HVAC')
queryset = queryset.exclude(tags__tag__tag_name__istartswith = 'HVAC')
but when I try the same with "Local", it excludes everything, not just the values that contain or start with the word "Local". Both examples below exclude everything:
queryset = queryset.exclude(tags__tag__tag_name__icontains = 'Local')
queryset = queryset.exclude(tags__tag__tag_name__istartswith = 'Local')
As an additional note, the following does work, but it only excludes that exact value and I can't anticipate / list all of the values that start with "Local":
queryset = queryset.exclude(tags__tag__tag_name = 'Local 123')
My best guess is that "Local" is a reserved word in python? Any ideas on ways around this or is there something else I'm missing?
I don't know if this is exactly the right way to deal with this issue, but since per #WillemVanOnsem it seems like I was excluding all model objects that have at least one tag with 'Local' in it (when using both exclude and filter as far as I can tell), instead I ended up creating a new list of all values that don't contain "Local" and returning that list instead of the original queryset.
newQueryset = list()
for item in list(queryset):
if not 'local' in str(item['tags__tag__tag_name']).lower():
newQueryset.append(item)
return newQueryset

Django Q object query filtering with multiple conditions failing

I am trying to apply multiple conditions to my filter. The model looks like this
class modelChat(models.Model):
source = models.ForeignKey(modelEmployer,related_name = 'rsource',on_delete=models.CASCADE,null=True,default=None,blank=True)
job = models.ForeignKey(modelJob,on_delete=models.CASCADE,null=True,default=None,blank=True)
destination = models.ForeignKey(modelEmployer,related_name = 'rdestination',on_delete=models.CASCADE,null=True,default=None,blank=True)
Initially I am trying to obtain an instance of chat that involves 2 parties based on a job. At one point source can be a destination and sometimes the destination can be the source. but the job remains the same.
This is what my query looks like
querySet = modelChat.objects.filter(
(Q(source=modelEmployerSourceInstance) | Q(destination=modelEmployerSourceInstance))
&
(Q(destination=modelEmployerDestinationInstance) | Q(destination=modelEmployerDestinationInstance))
&
Q(job_id=job_id)
)
The job id is correct and I know there is only one item in the DB. However this query alway returns back an empty item. Any suggestions why this is wrong and how I can fix it ?
I can't say for sure if that's the problem since you forgot to show what you really have in your DB but here:
(Q(destination=modelEmployerDestinationInstance) | Q(destination=modelEmployerDestinationInstance))
I assume you want:
(Q(source=modelEmployerDestinationInstance) | Q(destination=modelEmployerDestinationInstance))
instead...
Note that the logical would be much more obvious with shorter names, ie source and destination instead of modelEmployerSourceInstance modelEmployerDestinationInstance:
q = (
(Q(source=source) | Q(destination=source))
& (Q(source=destination | Q(destination=destination))
& Q(job_id=job_id)
)
querySet = modelChat.objects.filter(q)
Meaningful names are a good thing, but they have to be short and distinct enough. With "modelEmployerXXXInstance", you have four words to parse, and with the only distinctive (hence relevant) part of the name being in third position, your brain tends to skip over this part. The "model", "Employer" and "Instance" parts are actually just noise.

Find value matching value in a list of dicts

I have a list of dicts that looks like this:
serv=[{'scheme': 'urn:x-esri:specification:ServiceType:DAP',
'url': 'http://www.esrl.noaa.gov/psd/thredds/dodsC/Datasets/air.mon.anom.nobs.nc'},
{'scheme': 'urn:x-esri:specification:ServiceType:WMS',
'url': 'http://www.esrl.noaa.gov/psd/thredds/wms/Datasets/air.mon.anom.nobs.nc?service=WMS&version=1.3.0&request=GetCapabilities'},
{'scheme': 'urn:x-esri:specification:ServiceType:WCS',
'url': 'http://ferret.pmel.noaa.gov/geoide/wcs/Datasets/air.mon.anom.nobs.nc?service=WCS&version=1.0.0&request=GetCapabilities'}]
and I want to find the URL corresponding to the ServiceType:WMS which means finding the value of url key in the dictionary from this list where the scheme key has value urn:x-esri:specification:ServiceType:WMS.
So I've got this that works:
for d in serv:
if d['scheme']=='urn:x-esri:specification:ServiceType:WMS':
url=d['url']
print url
which produces
http://www.esrl.noaa.gov/psd/thredds/wms/Datasets/air.mon.anom.nobs.nc?service=WMS&version=1.3.0&request=GetCapabilities
but I've just watched Raymond Hettinger's PyCon talk and at the end he says that that if you can say it as a sentence, it should be expressed in one line of Python.
So is there a more beautiful, idiomatic way of achieving the same result, perhaps with one line of Python?
Thanks,
Rich
The serv array you listed looks like a dictionary mapping schemes to URLs, but it's not represented as such. You can easily convert it to a dict using list comprehensions, though, and then use normal dictionary lookups:
url = dict([(d['scheme'],d['url']) for d in serv])['urn:x-esri:specification:ServiceType:WMS']
You can, of course, save the dictionary version for future use (at the cost of using two lines):
servdict = dict([(d['scheme'],d['url']) for d in serv])
url = servdict['urn:x-esri:specification:ServiceType:WMS']
If you're only interested in one URL, then you can build a generator over serv and use next with a default value for the cases where a match isn't found, eg:
url = next((dct['url'] for dct in serv if dct['scheme'] == 'urn:x-esri:specification:ServiceType:WMS'), 'default URL / not found')
I would split this into two lines, to separate the target from the url retrieval. This is because your target may change in time, so this should not be hardwired. The single line of code follows.
I would use in instead of == as we want to search for all schemes that are of this type. This adds more flexibility, and readability, assuming this will not also catch other schemes not wanted. But from the description, this is the functionality desired.
target = "ServiceType:WMS"
url = [d['url'] for d in serv if target in d['scheme']]
Also, note, this returns a list in all cases, in case there is more than one match, so you will have to loop over url in the code that uses this.
How about this?
urls = [d['url'] for d in serv if d['scheme'] == 'urn:x-esri:specification:ServiceType:WMS']
print urls # ['http://www.esrl.noaa.gov/psd/thredds/wms/Datasets/air.mon.anom.nobs.nc?service=WMS&version=1.3.0&request=GetCapabilities']
Its doing the same thing your code is doing, where d['url'] are being appended to the list - urls if they end with WMS
You can even add an else clause:
urls = [i['url'] for i in serv if i['scheme'].endswith('WMS') else pass]
I've been trying to work in more functional programming into my own work, so here is a pretty simple functional way:
needle='urn:x-esri:specification:ServiceType:WMS'
url = filter( lambda d: d['scheme']==needle, serv )[0]['url']
filter takes as arguments a function that returns a boolean and a list to be filtered. It returns a list of elements that return True when passed to the boolean-returning function (in this case a lambda I defined on the fly). So, to finally get the url, we have to take the zeroth element of the list that filter returns. Since that is the dict containing our desired url, we can tag ['url'] on the end of the whole expression to get the corresponding dictionary entry.

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)

SQLAlchemy: Select count of related many-to-many elements

I have a many to many relationship between two tables/objects: Tag and Content. Tag.content is the relationship from a tag to all content which has this tag.
Now I'd like to find out the number of content objects assigned to a tag (for all tags, otherwise I'd simply use len()). The following code almost works:
cnt = db.func.count()
q = db.session.query(Tag, cnt) \
.outerjoin(Tag.content) \
.group_by(Tag) \
.order_by(cnt.desc())
However, it will never return a zero count for obvious reasons - there is at least one row per tag after all due to the LEFT JOIN used. This is a problem though since I'd like to get the correct count for all tags - i.e. 0 if a tag is orphaned.
So I wonder if there's a way to achieve this - obviously without sending n+1 queries to the database. A pure-SQL solution might be ok too, usually it's not too hard to map such a solution to SA somehow.
.filter(Tag.content.any()) removes the results with the incorrect count, but it will do so by removing the rows from the resultset altogether which is not what I want.
Solved it. I needed to use DISTINCT in the COUNTs:
cnt = db.func.count(db.distinct(Content.id))

Categories