Django QuerySet to search to different fields - django

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)

Related

Getting count of one column after a distinct on two columns

Here is a simplified representation of my models:
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
template_id = models.IntegerField(null=True)
...
What I want to do is show the number of times a template has been used by users. So when I list out the templates, I want to be able to say Used by X users. The main draw is that I don't only want to count a user once (so if a user uses a template twice, they still count as "one use case"). All stackoverflow posts talk about doing something like this:
counts = Post.objects.all().values("template_id").order_by().annotate(count=Count("template_id"))
But that obviously double counts a user that uses the same template twice. I was able to do a distinct on template_id and user pairings like so:
Post.objects.all().values("template_id", "user__id").distinct()
# Printing this out, I get 2 distinct entries in the QuerySet:
# <QuerySet [{'template_id': 1, 'user__id': 1}, {'template_id': 1, 'user__id': 2}]>
However, when I try to get the counts of template_id (the code below), it seems to ignore the distinct and still double counts users.
Post.objects.all().values("template_id", "user__id").distinct().values("template_id").annotate(count=Count("template_id"))
# Printing this out I get `count` = 3, which double counts a user.
# <QuerySet [{'template_id': 1, 'count': 3}]>
For what it's worth, I wrote a quick test case which is what is failing.
user1 = baker.make("User")
user2 = baker.make("User")
# Populate posts
quest1 = baker.make("post.Post", user=user1, template_id=1)
quest2 = baker.make("post.Post", user=user1, template_id=1) # Duplicate shouldn't count
quest3 = baker.make("post.Post", user=user2, template_id=1)
Got it to work using Django's built in ORM by doing the following:
template_ids = [] # My templates
# Get the number of times each template_id was used.
top_template_counts = (
Post.objects.filter(template_id__in=template_ids)
.values("template_id") # groups by template ids
.annotate(user_count=Count("user", distinct=True)) # Gets the number of users using each template
.order_by("-user_count")
)
# Accessing `top_template_counts`
for template_id_count in top_template_counts[:10]:
template_id = template_id_count["template_id"]
count = template_id_count["parent_count"]
why you just do not use:
counts = Post.objects.all().values("template_id", "user__id").distinct().values("template_id").count()

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].

Django lookup_expr for arrayfield not matching exact string

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';

Django - filter queryset disregarding spaces

I have a model that contains phone numbers external_number, stored as a char field:
models.py
class PhoneRecord(models.Model):
def __unicode__(self):
return "Call to %s (%s)" % (self.external_number, self.call_date.strftime("%c"))
INBOUND = "I"
OUTBOUND = "O"
DIRECTION_CHOICES = (
(INBOUND, "Inbound"),
(OUTBOUND, "Outbound"),
)
external_number = models.CharField(max_length=20)
call_date = models.DateTimeField()
external_id = models.CharField(max_length=20)
call_duration = models.TimeField()
call_direction = models.CharField(max_length=1, choices=DIRECTION_CHOICES, default=INBOUND)
call = models.FileField(upload_to='calls/%Y/%m/%d')
The form is cleaning and storing the data using the UKPhoneNumberField from https://github.com/g1smd/django-localflavor-UK-forms/blob/master/django/localflavor/uk/forms.py
This means that the number is stored in the database in the format 01234 567890 i.e. with a space in.
I have created a filter using django-filters which works well, when searching for partial phone number except that it doesn't filter correctly when the search term doesn't include the space. i.e.
search for 01234 returns the record for the example above
search for 567890 returns the record for the example above
search for 01234 567890 returns the record for the example above
search for 01234567890 does not return the record for the example above
Now, I could subject the form on the filter to the same restrictions (i.e. UKPhoneNumberField as the input screen, but that then takes away the ability to search for partial phone numbers.
I have also explored the possibility of using something like django-phonenumber-field that will control both the model and the form, but the validation provided by UKPhoneNumberField allows me to validate based on the type of number entered (e.g. mobile or geographical).
What I would ideally like is either
My filter to ignore spaces that are either input by the user in their search query, or any spaces that are in the stored value. Is this even possible?
Apply the validation provided by UKPhoneNumberField to another field type without having to analyse and re-write all the regular expressions provided.
Some other UK Phone number validation I have not found yet!
You could setup the filter to do something like this:
phone_number = "01234 567890"
parts = phone_number.split(" ")
PhoneRecord.objects.filter(
external_number__startswith="{} ".format(parts[0]),
external_number__endswith=" {}".format(parts[1]),
)
That way the filter is looking for the first half of the number with the space and then the second half of the number with the space as well. The only records that would be returned would be ones that had the value of "01234 567890"
I ended up adding a custom filter with a field_class = UKPhoneFilter. This means I can't search on partial numbers, but that was removed as a requirement from the project.
You should think about normalizing the data before you save them into your DB.
You could just use django-phonenumber-field which does just that.
In regarding your problem you can always use django's regex field postfix to query over regular expression.
e.g. MyModel.objects.filter(myfiel__regex=r'[a-Z]+')

Django search objects by multiple keys

I am trying to prepare search form where user is able to type 1, 2 or all (3 in this case) search filters.
Lets say that search filters are:
last name, phone and address. I am trying to filter queryset by:
if filterForm.is_valid():
last_name = filterForm.cleaned_data.get('last_name')
phone= filterForm.cleaned_data.get('phone')
address = filterForm.cleaned_data.get('address')
if last_name is None and phone is None and address is None:
pass
#we dont do search id db
else:
clients = Client.objects.filter(Q(last_name__contains=last_name) | Q(phone=phone) | Q(address__contains=address))
Each search key may be blank.
Unfortunately, it returns more results then expected. When I type in search filter "Example" as last name field, it returns all fields with this last name + many others rows.
Any idea how to fix this search issue?
I believe that your search returns more results than expected when any of the search keys are blank since a blank key will match any row with a value.
By only filtering on keys that contains a value it should work better.
Here is one example of how it can be done:
if filterForm.is_valid():
last_name = filterForm.cleaned_data.get('last_name')
phone= filterForm.cleaned_data.get('phone')
address = filterForm.cleaned_data.get('address')
import operator
predicates = []
if last_name:
predicates.append(Q(last_name__contains=last_name))
if phone:
predicates.append(Q(phone=phone))
if address:
predicates.append(Q(address__contains=address))
if len(predicates) == 0:
# Nothing to search for
pass
else:
clients = Client.objects.filter(reduce(operator.or_, predicates))
The code above will dynamically add filters that shall be added to the query. The usage of oprator.or_ will concatenate the statements with OR (=at least one statement needs to be satisfied). If you instead want all statements to be satisfied you can use operator.and_ instead.