Actually i have a working code but the issue that i am facing is how to sort the result of the queryset based on multiple rule. This is my models.py :
class Node(MPTTModel):
parent = TreeForeignKey('self', on_delete=models.CASCADE, blank=True, null=True, related_name='children')
name = models.TextField(blank=True, null=True)`
viewed_by = models.ManyToManyField(CustomUser, related_name='viewed_by', blank=True)
bookmarked_by = models.ManyToManyField(CustomUser, related_name='bookmarked_by', blank=True)
thumbs_up = models.ManyToManyField(CustomUser, related_name='thumbs_up', blank=True)
In my views.py i have managed to queryset the database and show all the results based on all matching words, but the missing point here is that i have managed only to sort the result by the number of bookmarks. For i.e : i have this two objects :
Object 1 : name = How to use django forms ?
Object 2 : name = Using django forms for searching the database.
With object 1 is bookmarked by 20 users and Object 2 is bookmarked by 10 users and i type in my search bar : Using django forms database In the result i have the first object as the first answer shown in the list even if the second one have much more matchs with the searched keywords. So what i want to do here is to sort the result first based on the number of matching keywords and than sort it by number of bookmarks. This my view so far :
search_text_imported = request.session['import_search_text']
if search_text_imported != '':
result_list = []
get_result_list = [x for x in search_text_imported.split() if len(x) > 2]
for keyword in get_result_list:
tree_list = Node.objects.filter((Q(name__icontains=keyword) | Q(Tags__icontains=keyword)), tree_type='root', published=True ).annotate(num_bookmarks=Count('bookmarked_by')).order_by('-num_bookmarks')
result_list += list(tree_list)
result = list(OrderedDict.fromkeys(result_list))
context = {
'tree_result': result,
}
Please let me know if there is something missing here, any help will be much appreciated.
The issue you are having is due to the fact you are creating the result list by concatenating query results together, it does not matter that the queries are sorted if you are concatenating them. You can change your code to only perform a single sorted query by first creating your Q filter and then passing it to a single query
filter = Q()
for keyword in keywords:
filter |= Q(name__icontains=keyword)
filter |= Q(Tags__icontains=keyword
result = Node.objects.filter(
filter,
tree_type='root',
published=True
).annotate(
num_bookmarks=Count('bookmarked_by')
).order_by('-num_bookmarks')
To order by the number of keywords that were matched is a difficult problem. A potential solution is to annotate each Node with a 1 or a 0 for each keyword depending on if there was a match or not and then sum them all
from functools import reduce
from operator import add
from django.db.models import Case, When, Value, F
cases = {}
for i, keyword in enumerate(keywords):
cases[f'keyword_match_{i}'] = Case(
When(name__icontains=keyword, then=1),
default=Value(0),
output_field=models.IntegerField(),
)
Node.objects.annotate(**cases).annotate(
matches=reduce(add, (F(name) for name in cases))
).order_by('-matches')
All together
filter = Q()
cases = {}
for i, keyword in enumerate(keywords):
filter |= Q(name__icontains=keyword)
filter |= Q(Tags__icontains=keyword
# Case is basically an "if" statement
# If the condition matches then we set the annotated value to 1
cases[f'keyword_match_{i}'] = Case(
When(name__icontains=keyword, then=1),
default=Value(0),
output_field=models.IntegerField(),
)
result = Node.objects.filter(
filter,
tree_type='root',
published=True
).annotate(
**cases
).annotate(
num_bookmarks=Count('bookmarked_by'),
keywords_matched=reduce(add, (F(name) for name in cases))
).order_by('-keywords_matched', '-num_bookmarks')