Looping over parallel lists in python - list

Im reading over an itunes library file. I scraped the artist names and songs and put them in parallel lists, one containing artist names and another containing artist songs. I would like to do this by using only lists
artist_choice = input("Enter artist name: ")
artist_names = [Logic, Kanye West, Lowkey, Logic, Logic]
artist_songs = [Underpressure, Stronger, Soundtrack to the struggle, Ballin, Im the man]
Say the user inputs the artist name Logic, how would i loop through the parallel list and print out every song associated with the artist Logic? if the user entered Logic the output should be:
Underpressure
Ballin
Im the man

This is sudo code for how to do it, I actually don't know much python.
results = [];
for (i=0;i<artist_names.length();i++): 1
if artist_names[i] == artist_choice:
results.push(artist_songs[i])
but as #Carcigenicate said, there are better ways of going about this. If you are going to be making many searches on these lists you may want to first loop through and group the data into a hash table or what #Carcigenicate suggests.
#RPGillespie 's link explains how to combine them into a hash table, this is a much better way of searching.

Rich beat me to it, but I'll post a more pythonic example:
def getSongs(listOfArtists,listOfSongs,artistToLookup):
songs = []
for artist,song in zip(listOfArtists,listOfSongs):
if (artist == artistToLookup):
songs.append(song)
return songs
Note using zip lets you iterate both lists at once fairly cleanly (without the need to subscript).

Related

Filter multiple Django model fields with variable number of arguments

I'm implementing search functionality with an option of looking for a record by matching multiple tables and multiple fields in these tables.
Say I want to find a Customer by his/her first or last name, or by ID of placed Order which is stored in different model than Customer.
The easy scenario which I already implemented is that a user only types single word into search field, I then use Django Q to query Order model using direct field reference or related_query_name reference like:
result = Order.objects.filter(
Q(customer__first_name__icontains=user_input)
|Q(customer__last_name__icontains=user_input)
|Q(order_id__icontains=user_input)
).distinct()
Piece of a cake, no problems at all.
But what if user wants to narrow the search and types multiple words into search field.
Example: user has typed Bruce and got a whole lot of records back as a result of search.
Now he/she wants to be more specific and adds customer's last name to search.So the search becomes Bruce Wayne, after splitting this into separate parts I'm having Bruce and Wayne. Obviously I don't want to search Orders model because order_id is a single-word instance and it's sufficient to find customer at once so for this case I'm dropping it out of query at all.
Now I'm trying to match customer by both first AND last name, I also want to handle the scenario where the order of provided data is random, to properly handle Bruce Wayne and Wayne Bruce, meaning I still have customers full name but the position of first and last name aren't fixed.
And this is the question I'm looking answer for: how to build query that will search multiple fields of model not knowing which of search words belongs to which table.
I'm guessing the solution is trivial and there's for sure an elegant way to create such a dynamic query, but I can't think of a way how.
You can dynamically OR a variable number of Q objects together to achieve your desired search. The approach below makes it trivial to add or remove fields you want to include in the search.
from functools import reduce
from operator import or_
fields = (
'customer__first_name__icontains',
'customer__last_name__icontains',
'order_id__icontains'
)
parts = []
terms = ["Bruce", "Wayne"] # produce this from your search input field
for term in terms:
for field in fields:
parts.append(Q(**{field: term}))
query = reduce(or_, parts)
result = Order.objects.filter(query).distinct()
The use of reduce combines the Q objects by ORing them together. Credit to that part of the answer goes to this answer.
The solution I came up with is rather complex, but it works exactly the way I wanted to handle this problem:
search_keys = user_input.split()
if len(search_keys) > 1:
first_name_set = set()
last_name_set = set()
for key in search_keys:
first_name_set.add(Q(customer__first_name__icontains=key))
last_name_set.add(Q(customer__last_name__icontains=key))
query = reduce(and_, [reduce(or_, first_name_set), reduce(or_, last_name_set)])
else:
search_fields = [
Q(customer__first_name__icontains=user_input),
Q(customer__last_name__icontains=user_input),
Q(order_id__icontains=user_input),
]
query = reduce(or_, search_fields)
result = Order.objects.filter(query).distinct()

Ordering dictionary in context Django

I looked at this thread, with an example of sorting dictionaries.
I have a dictionary of programme objects where the key is a programme object and the value is a lookup of the number of related Project objects.
def DepartmentDetail(request, pk):
department = Department.objects.get(pk=pk)
programmes = Programme.objects.all().filter(department=department).exclude(active=False).order_by('long_name')
combi = {}
for p in programmes:
prj = Project.objects.all().filter(programme=p)
combi[p] = str(len(prj))
return render(request, 'sysadmin/department.html',{'department': department, 'programmes': programmes, 'combi': sorted(combi.items())})
In the model, Programme returns a string 'long_name', so I believe that I am trying to sort a string key and a string value.
In the template I get to the keys and values as so,
{% for programme, n in combi %}
This gives me the error..
unorderable types: Programme() < Programme()
I don't really understand the error, in the python 3 documentation it states that the sorted() method accepts any iterable - So why does this happen?
I'm looking at collections.OrderedDict to solve the problem, but I want to know why this doesn't work.
Thanx.
Databases with index on columns are really good at sorting. There's almost never a need to sort on the client side. You can almost always do it in the server. The funny part it you apparently know how to do it too.
....exclude(active=False).order_by('long_name') # <--- this
Guess what, your data is already sorted there isn't a need to sort it again inside python!!
But there is a much bigger issue in your code. You are fetching a set of Project items and then looping through that set to retrieve them all over again one by one. So if you have 200 Project items you are doing 200 queries when one query does the job just as well. Just add select_related or prefetch_related depending on which direction you have the relationship.
Your code ideally should be something like this
department = Department.objects.get(pk=pk)
programmes = Programme.objects.all().filter(department=department).exclude(active=False).order_by('long_name')
return render(request, 'sysadmin/department.html',{'department': department, 'programmes': programmes,})
As far as I can see combi just contains duplicated data. The same thing can be accessed from programmes eg. programme.project_set.all()
(again this depends on which direction you have the relationship, your models are not shown)
Recommended reading: https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey
The issue is that sorted expects a way to be able to order the items and by default there isn't any way to know how to order your objects. You can provide a key
sorted(combi.items(), key=lambda i: i.long_name)

Filtering on the concatenation of two model fields in django

With the following Django model:
class Item(models.Model):
name = CharField(max_len=256)
description = TextField()
I need to formulate a filter method that takes a list of n words (word_list) and returns the queryset of Items where each word in word_list can be found, either in the name or the description.
To do this with a single field is straightforward enough. Using the reduce technique described here (this could also be done with a for loop), this looks like:
q = reduce(operator.and_, (Q(description__contains=word) for word in word_list))
Item.objects.filter(q)
I want to do the same thing but take into account that each word can appear either in the name or the description. I basically want to query the concatenation of the two fields, for each word. Can this be done?
I have read that there is a concatenation operator in Postgresql, || but I am not sure if this can be utilized somehow in django to achieve this end.
As a last resort, I can create a third column that contains the combination of the two fields and maintain it via post_save signal handlers and/or save method overrides, but I'm wondering whether I can do this on the fly without maintaining this type of "search index" type of column.
The most straightforward way would be to use Q to do an OR:
lookups = [Q(name__contains=word) | Q(description__contains=word)
for word in words]
Item.objects.filter(*lookups) # the same as and'ing them together
I can't speak to the performance of this solution as compared to your other two options (raw SQL concatenation or denormalization), but it's definitely simpler.

complex query in django view from different models

I have three models, Which has some common but not exact fields, from a single view ex. home I am retrieving like this
interviews = Interviews.objects.all().order_by('pub_date')[:3]
publications = Publications.objects.all().order_by('pub_date')[:3]
published = Published.objects.all().order_by('pub_date')[:3]
From home view I want them to show in template in such order that all the latest/New entries associated with these models will be in top.
like if interview entry 10 is the most recent entry in these all models, then it will be first, then if the published one is second recent it will be second ... etc .etc
Can any one tell me how to do it?
Depending on your other requirements it may be a good idea to make them into sublcasses of a common superclass - containing the common elements.
It's hard to say if it's valid, but if you do you can query the different types of objects separately or together by calling
SuperClass.objects.all().order_by('-pub_date')[:9]
which will render the 9 first objects regerdless of what sublcass they are. Of course assuming the superclass is named SuperClass. Of course this does not insure that there are 3 of each model.
Another simple way of solving it - although admittedly not using a query would simply be to sort the lists.
entries = sorted(list(interviews) + list(publications) + list(published), key=lambda x: x.pub_date, reverse=True)
should work - basically turning them into lists and sorting them.
One possible way is using lambda to sort data, but costs are hiher since you will doing this to python, not DBMS...
lst = []
lst.extend(list(Interviews.objects.order_by('pub_date')[:10]))
lst.extend(list(Publications.objects.order_by('pub_date')[:10]))
lst.extend(list(Published.objects.order_by('pub_date')[:10]))
# take 10 records for each, since you could not know how many records will be picked from which table
# now order them...
lst.sort(lambda x, y: cmp(x.pub_date, y.pub_date))
# and reverse the order, so newset is the first...
lst.reverse()
this will give you a list of objects ordered py pub_date, so you can slice the final list to get any number of records you want...
lst = lst[:10]

How to properly query a ManyToManyField for all the objects in a list (or another ManyToManyField)?

I'm rather stumped about the best way to build a Django query that checks if all the elements of a ManyToMany field (or a list) are present in another ManyToMany field.
As an example, I have several Persons, who can have more than one Specialty. There are also Jobs that people can start, but they require one or more Specialtys to be eligible to be started.
class Person(models.Model):
name = models.CharField()
specialties = models.ManyToManyField('Specialty')
class Specialty(models.Model):
name = models.CharField()
class Job(models.Model):
required_specialties = models.ManyToManyField('Specialty')
A person can start a job only if they have all the specialties that the job requires. So, again for the sake of example, we have three specialties:
Coding
Singing
Dancing
And I have a Job that requires the Singing and Dancing specialties. A person with Singing and Dancing specialties can start it, but another with Coding and Singing specialties cannot -- as the Job requires a Person who can both sing and dance.
So, now I need a way to find all jobs that a person can take on. This was my way to tackle it, but I'm sure there's a more elegant approach:
def jobs_that_person_can_start(person):
# we start with all jobs
jobs = Job.objects.all()
# find all specialties that this person does not have
specialties_not_in_person = Specialty.objects.exclude(name__in=[s.name for s in person.specialties])
# and exclude jobs that require them
for s in specialties_not_in_person:
jobs = jobs.exclude(specialty=s)
# the ones left should fill the criteria
return jobs.distinct()
This is because using Job.objects.filter(specialty__in=person.specialties.all()) will return jobs that match any of the person's specialties, not all of them. Using this query, the job that requires Singing and Dancing would appear for the singing coder, which is not the desired output.
I'm hoping this example is not too convoluted. The reason I'm concerned about this is that the Specialties in the system will probably be a lot more, and looping over them doesn't seem like the best way to achieve this. I'm wondering if anyone could lend a scratch to this itch!
Another Idea
Ok I guess I should have added this to the other answer, but when I started on it, it seemed like it was going to be a different direction haha
No need to iterate:
person_specialties = person.specialties.values_list('pk', flat=True)
non_specialties = Specialties.objects.exclude(pk__in=person_specialties)
jobs = Job.objects.exclude(required_specialties__in=non_specialties)
note: I don't know exactly how fast this is. You may be better off with my other suggestions.
Also: This code is untested
I think you should look at using values_list to get the person's specialties
Replace:
[s.name for s in person.specialties]
with:
person.specialties.values_list('name', flat=True)
That will give you a plain list (ie. ['spec1', 'spec2', ...]) which you can use again. And the sql query used in the bg will also be faster because it will only select 'name' instead of doing a select * to populate the ORM objects
You might also get a speed improvement by filtering jobs that the person definately can NOT perform:
so replace:
jobs = Job.objects.all()
with (2 queries - works for django 1.0+)
person_specialties = person.specialties.values_list('id', flat=True)
jobs = Job.objects.filter(required_specialties__id__in=person_specialties)
or with (1 query? - works for django1.1+)
jobs = Job.objects.filter(required_specialties__in=person.specialties.all())
You may also get an improvement by using select_related() on your jobs/person queries (since they have a foreign key that you're using)