Django lookup_expr for arrayfield not matching exact string - django

I have a djago array field,
names = ArrayField(
CICharField(max_length=255),
blank=True,
null=True,
help_text="Additional names of the person.",
)
It has names,
"names": [
"Adam Bender",
"April Watkins",
"Daniel Brooks",
"George Booker"
]
When I do,
names = django_filters.CharFilter(lookup_expr="icontains")
and search for "Adam Bender" it matches the record.
But when I search for "Adam Bend" then also it is matching.
I don't want that to happened. It should match exact name.
I tried iexact but it did not work.
Please advise.

I found the solution.
names = django_filters.CharFilter(method='names_filter')
def names_filter(self, queryset, name, value):
return queryset.filter(names__contains=[value])

you should use exact instead of iexact.
exact will return you the exact match.
Entry.objects.get(id__exact=14)
SELECT ... WHERE id = 14;
iexact use the like operator to match the results.
Blog.objects.get(name__iexact='beatles blog')
SELECT ... WHERE name ILIKE 'beatles blog';

Related

Django query set filter reverse startswith on charfield

Image some kind of product-rule which has 2 conditions:
name are equal
sku's have partial match, starts with.
The rule model looks like this:
class CreateAndAssignRule(models.Model):
name_equals = models.CharField(max_length=100)
sku_starts_with = models.CharField(max_length=100
Now I want to fetch all of the rules with name Product 1 and match sku sku-b-292
class CreateAndAssignRuleQuerySet(QuerySet):
def filter_by_name_and_sku(self, name, sku):
# We're looking which of the rules have a matching name, and where the rule have a string which is the string of the sku provided.
rules = self.filter(name_equals=name)
approved_ids = []
for rule in rules:
# We're looping through the rules to find out which of them has the beginnings of the sku.
# a sku_starts_with field would contains value eg: 'sku-a' where as the search string would be the full sku 'sku-a-111'. We want to match 'sku-a-111' but not 'sku-b-222'.
if sku.startswith(rule.sku_starts_with):
approved.append(rule.id)
return self.filter(id__in=approved_ids)
although the above works, it's hardly efficient especially as the number of rule is starting to grow a lot.
How can I resolve this with a queryset? Filtering on __startswith doesn't do the trick as it the reverse.
Filter with:
from django.db.models import F, Value
class CreateAndAssignRuleQuerySet(QuerySet):
def filter_by_name_and_sku(self, name, sku):
return self.alias(
sku=Value(sku)
).filter(
name_equals=name,
sku__startswith=F('sku_starts_with')
)
We thus here inject the sku in the queryset, and then use this to work with a __startswith lookup [Django-doc].

Item in the array did not validate: Select a valid choice. ["Acamedic" is not one of the available choices

I am trying to use CharField with choices using postgres ArrayField. here is my code;
BOOK_CATEGORY = (
("Academic", "Academic"),
("Science Fiction", "Science Fiction"),
("For Student", "For Student"),
("Others", "Others"),
)
class Book(models.Model):
category = ArrayField(models.CharField(max_length=100, choices=BOOK_CATEGORY), blank=True, null=True)
Here is the error I get when I try to post a value; ["Acamedic", "For Student", "Others"];
The problem is from the syntax. I posted it as ["Academic", "For Student", "Others"]. It rather supposed to be something like Academic,For Student,Others(Note: Without any space separating the different choices and no brackets and also no quotes)

Partial Keyword Search and Ranking

Using Django and Postgres, I have an investment holding model like so:
class Holding(BaseModel):
name = models.CharField(max_length=255, db_index=True)
symbol = models.CharField(max_length=16, db_index=True)
fund_codes = ArrayField(models.CharField(max_length=16), blank=True, default=list)
...
That contains a list of approximately 70k US/CAN equity, mutual funds. I want to build an autocomplete search function that prioritizes 1) ranking of exact match of the symbol or fund_codes, followed by 2) Near matches on the symbol, then 3) Full text search of holding name.
If I have a search vector that adds more weight to the symbol and fund_codes:
from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank
from django.db.models import F, Func, Value
vector = SearchVector('name', weight='D') + \
SearchVector('symbol', weight='A') + \
SearchVector(Func(F('fund_codes'), Value(' '), function='array_to_string'), weight='A')
Then, searching 'MA'
Investment.objects \
.annotate(document=vector, rank=SearchRank(vector, query)) \
.filter(document__icontains='MA') \
.order_by('-rank') \
.values_list('name', 'fund_codes', 'symbol', 'rank',)
Doesn't give the results I need. I need MA (Mastercard) as top listing, Then MAS (Masco Corp), etc... Then listings containing 'MA' in the name field.
I've also looked at overriding SearchQuery with:
class MySearchQuery(SearchQuery):
def as_sql(self, compiler, connection):
params = [self.value]
if self.config:
config_sql, config_params = compiler.compile(self.config)
template = 'to_tsquery({}::regconfig, %s)'.format(config_sql)
params = config_params + [self.value]
else:
template = 'to_tsquery(%s)'
if self.invert:
template = '!!({})'.format(template)
return template, params
But still not getting results I need. Any suggestions on how I should approach search functionality in this use case? Perhaps concatenate an exact search query and a full-text search query?
What you need is to pass in normalization parameter. This will give higher ranking to names that are exact match. Raw query looks like following:
SELECT id, name, symbol, func_codes,
ts_rank_cd(to_tsvector(func_codes), to_tsquery('MA'), 2 ) as rank
FROM Holding
ORDER BY rank DESC
LIMIT 100;
Notice that I passed in a normalization parameter https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-RANKING
How to do it in Django?
I believe that django doesn't yet support passing normalization yet. I see a open ticket for it, but its 2 years old. Maybe no one has worked on it yet.
https://code.djangoproject.com/ticket/28194
You can use raw query for now. See official documentation on how:
https://docs.djangoproject.com/en/2.2/topics/db/sql/

Django QuerySet to search to different fields

I have the following code:
result = Invoice.objects.filter(
Q(client__in=Client.objects.filter(
Q(first_name__icontains=search_param) | Q(last_name__icontains=search_param))))
class Client(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
If both first and last name are typed, the search results in nothing. However, if only one of both is typed it works and that I can not understand since that is an OR statement with contains.
My logic is:
Joe Doe contains Joe, therefore the first condition matches and it should return the value found.
You'll need to split the search_param and search on each entry. Since Django does not execute a query until it is evaluated, you can simply append filters to your queryset.
result = Invoice.objects.all()
for chars in search_param.split():
result = result.filter(
Q(client__in=Client.objects.filter(
Q(first_name__icontains=chars)
| Q(last_name__icontains=chars)
)
)
# This should show all who matched any name entered,
# even if you enter last name first.
print(results)

Filter from comma sep. list in django model "whole word"

Can anyone help with this, I have a model like this:
class Searchpage(models.Model):
slug = models.SlugField(max_length=255, unique=True)
keywords = models.CharField(max_length=255, blank=True, null=True)
the "keywords" is a comma sep. list ex. "foo, foobar, foo bar"
Now I want to make a filter that match the words ...
Searchpage.objects.filter(keywords__contains="foo")
match correct
Searchpage.objects.filter(keywords__contains="bar")
match, but not correct... only whole word ..
I hope it makes sense ! ;)
I would probably use an extra model and a M2M relationship for the keywords to filter on. There are also dedicated packages for this like django-taggit.
But you could also use a regex filter:
Searchpage.objects.filter(keyword__regex=r'\bbar\b')
(haven't tested this, but I think it should work)