I have a choice filed and i need to sort it based on the order in CHOICES tuple and this model.py:
class MeetingMember(models.Model):
CHOICES = (
("H", "Host"),
("A", "Accepted"),
("R", "Rejected"),
("I", "Invited" )
)
status = models.CharField(max_length=9, choices=CHOICES, default="I")
i have already tried meta ordering :
class Meta:
ordering = ('status',)
but it is not working i need to sort it in Host,Accepted,Rejected,Invited
You can try to exploit the Replace function (https://docs.djangoproject.com/en/3.2/ref/models/database-functions/#replace). The strategy is to annotate a new field with a value built in a way that its alphabetical order matches the custom order you want in the original field:
from django.db.models import F, Value
from django.db.models.functions import Replace
# This generates a dictionary like {'0': 'H', '1': 'A', ...}
mapped_choices = {str(n): CHOICES[n][0] for n in range(len(CHOICES))}
# For each mapped choice we create a replacer
replacers = [
Replace('status_for_ordering', Value(original), Value(replacement))
for replacement, original in mapped_choices.items()
]
qs = MeetingMember.objects.all().annotate(status_for_ordering=F('status'))
for replacer in replacers:
qs = qs.annotate(status_for_ordering=replacer)
# Of course here you can still filter or do other operations before ordering
qs = qs.order_by('status_for_ordering')
This solution should work for your example but of course would need some adjustments in case the replacements starts to conflict each others (f.i. if one of your original status values contains a digit).
Related
I wanted to know if there is a way to insert a search bar in the Django choices, that is instead of manually searching the various choices if it is possible to use a filter bar to search for our choice in Django Admin - Models.
Well I think django-filter library provide you with maybe good facilities for this purpose, I have briefly given you some examples from it's documentation below:
ChoiceFilter
This filter matches values in its choices argument. The choices must be explicitly passed when the filter is declared on the FilterSet.
class User(models.Model):
username = models.CharField(max_length=255)
first_name = SubCharField(max_length=100)
last_name = SubSubCharField(max_length=100)
status = models.IntegerField(choices=STATUS_CHOICES, default=0)
STATUS_CHOICES = (
(0, 'Regular'),
(1, 'Manager'),
(2, 'Admin'),
)
class F(FilterSet):
status = ChoiceFilter(choices=STATUS_CHOICES)
class Meta:
model = User
fields = ['status']
TypedChoiceFilter
The same as ChoiceFilter with the added possibility to convert value to match against. This could be done by using coerce parameter. An example use-case is limiting boolean choices to match against so only some predefined strings could be used as input of a boolean filter:
import django_filters
from distutils.util import strtobool
BOOLEAN_CHOICES = (('false', 'False'), ('true', 'True'),)
class YourFilterSet(django_filters.FilterSet):
...
flag = django_filters.TypedChoiceFilter(choices=BOOLEAN_CHOICES,
coerce=strtobool)
MultipleChoiceFilter
The same as ChoiceFilter except the user can select multiple choices and the filter will form the OR of these choices by default to match items. The filter will form the AND of the selected choices when the conjoined=True argument is passed to this class.
Multiple choices are represented in the query string by reusing the same key with different values (e.g. ‘’?status=Regular&status=Admin’’).
TypedMultipleChoiceFilter
Like MultipleChoiceFilter, but in addition accepts the coerce parameter, as in TypedChoiceFilter.
See also:
django-filter [Docs]
django-filter [Github]
I have the following models:
## Tags for issues
class issueTags(models.Model):
name = models.CharField(max_length=400)
class issues(models.Model):
tags = models.ManyToManyField(issueTags,blank = True)
In my view I get an array from some client side JavaScript i.e.
(Pdb) array_data = request.POST['arr']
(Pdb) array_data
'["2","3"]'
How should I filter my issues object to find all issues which match all tags in the array? (the 2,3 are the ID values for tag__id.
If there is a better way to arrange the objects that would also work so I can search in this fashion.
At the time of writing this, the existing answers are either incorrect (e.g. filtering matching all Issues that have any of the specified tags and the correct tag count) or inefficient (e.g. attaching filters in a loop).
For the following models:
class IssueTag(models.Model):
name = models.CharField(max_length=400, blank=True)
class Issue(models.Model):
label = models.CharField(max_length=50, blank=True)
tags = models.ManyToManyField(IssueTag, related_name='issues')
I suggest using Django Annotation in conjunction with a filter like so:
from django.db.models import Count, Q
tags_to_match = ['tag1', 'tag2']
issues_containing_all_tags = Issue.objects \
.annotate(num_correct_tags=Count('tags',
filter=Q(tags__name__in=tags_to_match))) \
.filter(num_correct_tags=2)
to get all Issues that have all required tags (but may have additional tags, as is required in the question).
This will produce the following SQL query, that resolves all tag matching in a single IN clause:
SELECT "my_app_issue"."id", "my_app_issue"."label",
COUNT("my_app_issue_tags"."issuetag_id")
FILTER (WHERE "my_app_issuetag"."name" IN ('tag1', 'tag2'))
AS "num_correct_tags"
FROM "my_app_issue"
LEFT OUTER JOIN "my_app_issue_tags" ON ("my_app_issue"."id" = "my_app_issue_tags"."issue_id")
LEFT OUTER JOIN "my_app_issuetag" ON ("my_app_issue_tags"."issuetag_id" = "my_app_issuetag"."id")
GROUP BY "my_app_issue"."id", "my_app_issue"."label"
HAVING COUNT("my_app_issue_tags"."issuetag_id")
FILTER (WHERE ("my_app_issuetag"."name" IN ('tag1', 'tag2'))) = 2;
args=('tag1', 'tag2', 'tag1', 'tag2', 2)
I haven't tested this, but I think you could do the following:
from django.db.models import Q
array_data = array_data.split(',')
issues.objects.filter(
tags__in=array_data,
).exclude(
# Exclude any that aren't in array_data
~Q(tags__in=array_data)
).annotate(
matches=Count(tags, distinct=True)
).filter(
# Make sure the number found is right.
matches=len(array_data)
)
FYI, you should be using Issue, IssueTag for your model names to follow Django's naming pattern.
It isn't most elegant solution or pythonic but I ended up just looping around the resulting filter.
def filter_on_category(issue_object,array_of_tags):
#keep filtering to make an and
i = 0
current_filter = issue_object
while (i < (len(array_of_tags))):
#lets filter again
current_filter=current_filter.filter(tags__id__in=array_of_tags[i])
i=i+1
return current_filter
Django field lookups argument (__) for many-to-many fields needs list argument. I have created a dummy list for each array element of IssueTags and pass it to lookups argument and it works as expected.
Let you have this models:
class IssueTags(models.Model):
name = models.CharField(max_length=400)
class Issues(models.Model):
tags = models.ManyToManyField(IssueTags,blank = True)
You want to get Issues which contains all of these IssueTags = ["1","2","3"]
issue_tags_array = ["1","2","3"]
#First initialize queryset
queryset = Issues.objects.all()
i = 0
while i < len(issue_tags_array):
#dummy issue_tag list
issue_tag = [issue_tags_array[i]]
#lets filter again
queryset = queryset.filter(tags__id__in=issue_tag)
i=i+1
return queryset
I am working on creating a cocktail recipe app as a learning exercise.
I am trying to create a filter through Django's Rest Framework that accepts a string of ingredient IDs through a query parameter (?=ingredients_exclusive=1,3,4), and then searches for all recipes that have all of those ingredients. I would like to search for “All cocktails that have both rum and grenadine” and then also, separately “All cocktails that have rum, and all cocktails that have grendaine.”
The three models in my app are Recipes, RecipeIngredients, and IngredientTypes. Recipes (Old Fashioned) have multiple RecipeIngredients (2oz of Whiskey), and RecipeIngredients are all of a Ingredient Type (Whiskey). I will eventually change the RecipeIngredient to a through model depending on how far I decide to take this.
The list can be of a variable length, so I cannot just chain together filter functions. I have to loop through the list of ids and then build a Q().
However, I'm having some issues. Through the Django Shell, I have done this:
>>> x = Recipe.objects.all()
>>> q = Q(ingredients__ingredient_type=3) & Q(ingredients__ingredient_type=7)
>>> x.filter(q)
<QuerySet []>
>>> x.filter(ingredients__ingredient_type=3).filter(ingredients__ingredient_type=7)
<QuerySet [<Recipe: Rum and Tonic>]>
So here's my question: Why is the Q object that ANDs the two queries different than the chained filters of same object?
I've read through the "Complex lookups with Q objects" in the Django documentation and it doesn't seem to help.
Just for reference, here are my filters in Filters.py.
The "OR" version of this command is working properly:
class RecipeFilterSet(FilterSet):
ingredients_inclusive = django_filters.CharFilter(method='filter_by_ingredients_inclusive')
ingredients_exclusive = django_filters.CharFilter(method='filter_by_ingredients_exclusive')
def filter_by_ingredients_inclusive(self, queryset, name, value):
ingredients = value.split(',')
q_object = Q()
for ingredient in ingredients:
q_object |= Q(ingredients__ingredient_type=ingredient)
return queryset.filter(q_object).distinct()
def filter_by_ingredients_exclusive(self, queryset, name, value):
ingredients = value.split(',')
q_object = Q()
for ingredient in ingredients:
q_object &= Q(ingredients__ingredient_type=ingredient)
return queryset.filter(q_object).distinct()
class Meta:
model = Recipe
fields = ()
I've also included my models below:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
class IngredientType(models.Model):
name = models.CharField(max_length=256)
CATEGORY_CHOICES = (
('LIQUOR', 'Liquor'),
('SYRUP', 'Syrup'),
('MIXER', 'Mixer'),
)
category = models.CharField(
max_length=128, choices=CATEGORY_CHOICES, default='MIXER')
def __str__(self):
return self.name
class Recipe(models.Model):
name = models.CharField(max_length=256)
def __str__(self):
return self.name
class RecipeIngredient(models.Model):
ingredient_type = models.ForeignKey(IngredientType, on_delete=models.CASCADE, related_name="ingredients")
quantity = models.IntegerField(default=0)
quantity_type = models.CharField(max_length=256)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name="ingredients")
#property
def ingredient_type_name(self):
return self.ingredient_type.name
#property
def ingredient_type_category(self):
return self.ingredient_type.category
def __str__(self):
return f'{self.quantity}{self.quantity_type} of {self.ingredient_type}'
Any help would be very much appreciated!
The difference between the two approaches to filter() is described in Spanning multi-valued relationships:
Everything inside a single filter() call is applied simultaneously to filter out items matching all those requirements.... For multi-valued relations, they apply to any object linked to the primary model, not necessarily those objects that were selected by an earlier filter() call.
The example in the documentation makes it more clear. I'll rewrite it in terms of your problem:
To select all recipes that contain an ingredient with both type 3 and type 7, we would write:
Recipe.objects.filter(ingredients__ingredient_type=3, ingredients__ingredient_type=7)
That is of course impossible in your model, so this would return an empty queryset, just like your Q example with AND.
To select all recipes that contain an ingredient with type 3 as well as an ingredient with type 7, we would write:
Recipe.objects.filter(ingredients__ingredient_type=3).filter(ingredients__ingredient_type=7)
It's not especially intuitive, but they needed a way to distinguish these two cases and this is what they came up with.
Back to your problem, the OR case can be made simpler by using the in operator:
Recipe.objects.filter(ingredients__ingredient_type__in=[3, 7]).distinct()
The AND case is complicated because it's a condition that involves multiple rows. A simple approach would be to just take the OR version above and further process it in Python to find the subset that has all the ingredients.
A query approach that should work involves annotation with Count. This is untested, but something like:
Recipe.objects.annotate(num_ingredients=Count("ingredients",
filter=Q(ingredients__ingredient_type__in=[3, 7]))
.filter(num_ingredients=2)
Another approach to the AND case for Django 1.11+ would be to use the relatively new QuerySet intersection() method. As per the docs, this method:
Uses SQL’s INTERSECT operator to return the shared elements of two or more QuerySets.
So given an arbitrary list of IngredientType primary keys, you could create a filter() query for each pk (let's call these subqueries) and then spread that list (the * operator) into the intersection() method.
Like so:
# the base `QuerySet` and `IngredientType` pks to filter on
queryset = Recipe.objects.all()
ingredient_type_pks = [3, 7]
# build the list of subqueries
subqueries = []
for pk in ingredient_type_pks:
subqueries.append(queryset.filter(ingredients__ingredient_type__pk=pk))
# spread the subqueries into the `intersection` method
return queryset.intersection(*subqueries).distinct()
I added distinct() in there just to be safe and avoid duplicate results, but I am actually not certain whether it's necessary. Will have to test and update this post later.
I have this field:
operation = models.CharField(max_length=10, choices=OPERATIONS)
Having this filter works:
class OperationFilter(django_filters.Filter):
def filter(self, qs, value):
try:
qs = qs.filter(operation=value.upper())
except:
pass
return qs
With url:
/api/v1/operation/?operation=CREATE
But having the default filter (without an extra OperationFilter) fails with:
{
"operation": [
"Select a valid choice. %(value)s is not one of the available choices."
]
}
Why is a filter on a field with choices failing?
For other, non-choice fields, default filters are working fine:
/api/v1/operation/?recipient=recipient-19
EDIT
The OPERATIONS:
from enum import Enum
def enum_as_choices(enum_class):
"""From an enum class, generate choices for a django field"""
return ((entry, entry.value) for entry in enum_class)
class OperationType(Enum):
CREATE = 'CREATE'
STATUS = 'STATUS'
EXPAND = 'EXPAND'
DELETE = 'DELETE'
OPERATIONS = enum_as_choices(OperationType)
You are using django_filters package, I suggest reading docs, since you already have support for this
https://django-filter.readthedocs.io/en/master/ref/filters.html#choicefilter
Just point out your choices to the value suggested by the other answers (or check the example in docs)
The choices you written would be converted to this pythonic representation:
(
('OperationType.CREATE', 'CREATE'),
('OperationType.STATUS', 'STATUS'),
('OperationType.EXPAND', 'EXPAND'),
('OperationType.DELETE', 'DELETE')
)
As you can see the actual values stored in your operation field (in DB) are 'OperationType.CREATE', etc.
So you should change your choices to normal constant choices or you should filter by something like 'OperationType.CREATE' which is not a good option IMO.
also you can change your enum_as_choices method like this:
def enum_as_choices(enum_class):
"""From an enum class, generate choices for a django field"""
return ((entry.name, entry.value) for entry in enum_class)
You haven't defined a blank/default choice in your OPERATIONS. To do so, add something like this:
OPERATIONS = (
('', 'NONE'),
# the rest of your choices here...
)
But you would also need to update your model to be:
operation = models.CharField(max_length=10, choices=OPERATIONS, default='NONE')
I have a model of objects. I also have a list of options to filter results with. I'm not sure if there is an easy way to filter the objects in the model such that any object that matches any of the items in the filter list is returned. For example:
# returns all users with name starting with 'P'
usersWithPName = User.objects.filter(name__startswith = 'P')
# 3 letters to filter User model with
filterList = ['P', 'T', 'R']
# ideally would return all users with name starting with either 'P', 'T', or 'R'
usersWithPTRName = User.objects.filter(name__startswith = filterList)
Is there any way to filter (in this case) the User model such that any object matching any one of the items in the filterList is returned?
This can be done with Q objects
from django.db.models import Q
usersWithPTRName = User.objects.filter(Q(name__startswith='P') |
Q(name__startswith='T') |
Q(name__startswith='R'))
Also you can build Q filters at runtime:
filterList = ['P', 'T', 'R']
query = Q()
for letter in filterList:
query = query | Q(name__startswith=letter)
usersWithPTRName = User.objects.filter(query)
You can use python reduce and operator builtins:
import operator
from functools import reduce
from django.db.models import Q
values = ["blue", "green", "brown"]
# or condition
conditions = reduce(operator.or_, [Q(**{"categories__slug": value}) for value in values])
# and condition
conditions = reduce(operator.and_, [Q(**{"categories__slug": value}) for value in values])
queryset = queryset.filter(conditions)
Two choices.
Include the first letter as a property in your model.
Use more advanced queries.
You can do this
class User( models.Model ):
... all the usual stuff ...
#property
def first_letter( self ):
return self.name[:1]
Now can you filter with filter( first_letter__in=('P','T','R') )
The second choice is to build Django Q objects for your filter.
Start here: https://stackoverflow.com/search?q=django+Q+objects