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]
Related
I have a Django model called Format, that has some fields, and another model called DepartmentFormat that looks like this
class DepartmentFormat(models.Model):
department = models.ForeignKey(Department, on_delete=models.CASCADE)
format = models.ForeignKey(Format, on_delete=models.CASCADE, related_name='format_dptfmt')
status = models.ForeignKey(FormatStatus, on_delete=models.CASCADE, null=True, default=None)
In my view to display the formats, I have some filters, like this:
class FormatFilter(FilterUserMixin):
# SOME OTHER FILTERS
class Meta:
model = Format
fields = ("business_type", "rfc", "economic_sector", "cellphone",
"format_dptfmt__status__status")
However, the same Format can have multiple DepartmentFormat objects related to it, because they can have the same Format, but different Department.
For example, this is a possible case:
A format has two DepartmentFormat objects related to it. One of those has "Ecology department" as its department, and another has "Public works department" as its department.
There exists a third Department, which has still no DepartmentFormat object related to it.
So this same Format could have zero, one, two or three DepartmentFormat objects related to it, with each of the Department objects that exist.
What I'm after, is having three filters, one for each of the departments. The first one, for instance, could filter only those Formats for which there is a related DepartmentFormat that happens to have "Ecology department", and the status selected in the filter. I can do this properly by adding format_dptfmt__status__status to the fields in the Filter, since format_dptfmt is the related_name I specified.
However, I can only do this once, and I need to do it three times. I can handle the filtering itself by just making def filter_<field_name> methods, but I can't do that if I need three filters filtering the exact same field (format_dptfmt__status__status). Is there a way to add multiple filters that filter the same field?
I can achieve this by adding format_dptfmt__status__status multiple times in the ```fields", and then three identical filters render, but I cannot distinguish them and make filter methods for them, because they are all called the same.
So, either a name identifier for each filter, or another method altogether, is what I'm after, and I don't know how to try it another way.
This should solve your problem if I don't get it very wrong
from django.db.models import Q
model.objects.filter(Q(field1='x') | Q(field1='y'))
What about filtering this way:
list_of_options = ['x', 'y', 'z']
model.objects.filter(field__in=list_of_options)
At the end, I used:
fields = ("business_type", "rfc", "cellphone", "format_dptfmt__status__status_1", "format_dptfmt__status__status_2", "format_dptfmt__status__status_3")
And
format_dptfmt__status__status_1 = django_filters.ChoiceFilter(empty_label='---Ecología---', label='Ecología', field_name='format_dptfmt__status__status', choices=((1, 'Aceptado'), (2, 'Rechazado')), method='filter_format_dptfmt__status__status_1')
format_dptfmt__status__status_2 = django_filters.ChoiceFilter(empty_label='---Protección civil---', label='Protección civil', field_name='format_dptfmt__status__status', choices=((1, 'Aceptado'), (2, 'Rechazado')), method='filter_format_dptfmt__status__status_2')
format_dptfmt__status__status_3 = django_filters.ChoiceFilter(empty_label='---Obras públicas---', label='Obras públicas', field_name='format_dptfmt__status__status', choices=((1, 'Aceptado'), (2, 'Rechazado')), method='filter_format_dptfmt__status__status_3')
And then just filtered by each Department object in the method for each filter. I don't know why Django allowed this, since format_dptfmt__status__status_1 is not a field for the Format object, but just adding the three filters individually, and just using the same field_name for all of them, seemed to work.
In case I am asking the wrong question, let me first state the end goal: I need to allow users to filter a ListView by a field that's not in the primary model (Salesleadquote), but instead the field comes from a model (Salesleadbusinessgroup) with a FK to a related model (Saleslead).
The way I am trying to approach this is by annotating a field on Salesleadquote.
The models:
class Salesleadquote(models.Model):
salesleadquoteid = models.AutoField(db_column='SalesLeadQuoteId', primary_key=True)
salesleadid = models.ForeignKey(Saleslead, models.DO_NOTHING, db_column='SalesLeadId')
...
class Saleslead(models.Model):
salesleadid = models.AutoField(db_column='SalesLeadId', primary_key=True)
...
class Salesleadbusinessgroup(models.Model):
salesleadbusinessgroupid = models.AutoField(db_column='SalesLeadBusinessGroupId', primary_key=True)
salesleadid = models.ForeignKey(Saleslead, models.DO_NOTHING, db_column='SalesLeadId')
businessgroupid = models.ForeignKey(Businessgroup, models.DO_NOTHING, db_column='BusinessGroupId')
The desired result (queryset), in SQL:
SELECT slq.*, slbg.BusinessGroupId FROM crm.SalesLeadQuote slq
LEFT JOIN
(SELECT SalesLeadId, BusinessGroupId
FROM crm.SalesLeadBusinessGroup ) slbg
ON slbg.SalesLeadId = slq.SalesLeadId
WHERE slbg.BusinessGroupId IN (5,21)
I know I can get a RawQuerySet by doing something like
Salesleadquote.objects.raw("SELECT salesleadquote.*, \
salesleadbusinessgroup.businessgroupid \
FROM salesleadquote \
LEFT JOIN salesleadbusinessgroup \
ON salesleadquote.salesleadid = salesleadbusinessgroup.salesleadid \
WHERE salesleadbusinessgroup.businessgroupid IN (5,21)")
But I need the functionality of a QuerySet, so my idea was to annotate the desired field (businessgroupid) in Salesleadquote, but I've been struggling with how to accomplish this.
I implemented a work-around that doesn't address my original question but works for my use case. I created a view at the database level (called SalesLeadQuoteBG) using the SQL I had posted and then tied that to a model to use with Django's ORM.
class Salesleadquotebg(models.Model):
"""
This model represents a database view that extends the Salesleadquote table with a business group id column.
There can be multiple business groups per quote, resulting in duplicate quotes, but this is handled at the view and template layer
via filtering (users are required to select a business group).
"""
salesleadquoteid = models.IntegerField(db_column='SalesLeadQuoteId', primary_key=True) # Field name made lowercase.
salesleadid = models.ForeignKey(Saleslead, models.DO_NOTHING, db_column='SalesLeadId') # Field name made lowercase.
...
businessgroupid = models.ForeignKey(Businessgroup, models.DO_NOTHING, db_column='BusinessGroupId')
I am using django-filters for the filtering.
filters.py:
BG_CHOICES = (
(5, 'Machine Vision'),
(21, 'Process Systems'),
)
class BGFilter(django_filters.FilterSet):
businessgroupid = django_filters.ChoiceFilter(choices=BG_CHOICES)
class Meta:
model = Salesleadquotebg
fields = ['businessgroupid', ]
This can be done with pure Django ORM. Relationships can be followed backwards (filtering on salesleadbusinessgroup) and the double underscore syntax can be used to query attributes of the related model and also follow more relationships
Salesleadquote.objects.filter(
salesleadbusinessgroup__businessgroupid__in=(5,21)
)
I am using Relay, Django, Graphene Graphql.
I would like to use django_filters to filter for multiple arguments of type on accommodation. This is described in my schema file and atm looks like:
class AccommodationNode(DjangoObjectType) :
class Meta:
model = Accommodation
interfaces = (relay.Node,)
filter_fields = ['type']
This works perfectly if I pass a single string like: {"accommodationType": "apartment"}, but what if I want to filter for all accommodations that are apartments OR hotels? something like: {"accommodationType": ["apartment","hotel"]}
This is my model:
class Accommodation(models.Model):
ACCOMMODATION_TYPE_CHOICES = (
('apartment', 'Apartment'),
('host_family', 'Host Family'),
('residence', 'Residence'),
)
school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='accommodations')
type = models.CharField(
max_length=200,
choices=ACCOMMODATION_TYPE_CHOICES,
default='apartment'
)
def __str__(self):
return str(self.school) + " - " + self.type
Is there any way I can do this without writing custom filters as are suggested here? For only one filter field this is a great solution but I'll end up having around 50 throughout my application including linked objects...
Have a look at Django REST Framework Filters:
https://github.com/philipn/django-rest-framework-filters
It supports more than exact matches, like in, which you are looking for, but also exact, startswith, and many more, in the same style of Django's ORM. I use it frequently and have been impressed - it even integrates with DRF's web browseable API. Good luck!
like FlipperPA mentioned, I need to use 'in'. According to the django_filter docs:
‘in’ lookups return a filter derived from the CSV-based BaseInFilter.
and an example of BaseInFilter in the docs:
class NumberRangeFilter(BaseInFilter, NumberFilter):
pass
class F(FilterSet):
id__range = NumberRangeFilter(name='id', lookup_expr='range')
class Meta:
model = User
User.objects.create(username='alex')
User.objects.create(username='jacob')
User.objects.create(username='aaron')
User.objects.create(username='carl')
# Range: User with IDs between 1 and 3.
f = F({'id__range': '1,3'})
assert len(f.qs) == 3
The answer to my question:
class AccommodationNode(DjangoObjectType) :
class Meta:
model = Accommodation
interfaces = (relay.Node,)
filter_fields = {
'type': ['in']
}
With the argument {"accommodationType": "apartment,hotel"} will work
Url: /user?u=root works
class UserFilter(django_filters.rest_framework.FilterSet):
u = django_filters.rest_framework.CharFilter(name='username', lookup_expr='contains')
class Meta:
model = User
fields = ['username','u']
but when i changed it to
class UserFilter(django_filters.rest_framework.FilterSet):
u = django_filters.rest_framework.CharFilter(name='username', lookup_expr=['contains'])
class Meta:
model = User
fields = ['username','u']
url: /user?u__contains=root doesn't work.
django 1.11.1
django-filter 1.0.4
djangorestframework 3.6.3
Ykh is close, but incorrect. In your second example, the filter is still exposed as u, so filtering by u__contains is a no-op since it's not a recognized name. u__contains is not somehow translated into a u__contains__contains lookup.
Additionally, passing a list or tuple of lookups to lookup_expr might provide a different behavior than you would expect. It is not related to the automatic filter generation that you see with Meta.fields. Instead, it creates a multi-lookup filter (docs). This filter has two inputs:
a text input for the value to filter by
a select widget to select which lookup to use
It accomplishes this by using a django.forms.MultiWidget, so your query would need to be something like /user?u_0=root&u_1=contains.
In general, MultiWidgets are not that compatible with API usage, given the _0 and _1 suffixes.
If you're trying to expose a filter named u__contains, you should do something like:
class UserFilter(django_filters.rest_framework.FilterSet):
u = django_filters.rest_framework.CharFilter(name='username', lookup_expr='exact')
u__contains = django_filters.rest_framework.CharFilter(name='username', lookup_expr='contains')
class Meta:
model = User
fields = ['u', 'u__contains']
And there are several ways to use lookup_expr and the way you write it is incorrect would be "icontains" for contain and "iexact" for exact. Also another well used expressions would be gte and lte.
class UserFilter(django_filters.rest_framework.FilterSet):
u_contain = django_filters.rest_framework.CharFilter(
field_name='username',
lookup_expr='icontains'
)
l_exact = django_filters.rest_framework.CharFilter(
field_name='lastname',
lookup_expr='iexact'
)
class Meta:
model = User
fields = ['u_contain', 'l_exact']
I want to build an webapp like Quora or Medium, where a user can follow users or some topics.
eg: userA is following (userB, userC, tag-Health, tag-Finance).
These are the models:
class Relationship(models.Model):
user = AutoOneToOneField('auth.user')
follows_user = models.ManyToManyField('Relationship', related_name='followed_by')
follows_tag = models.ManyToManyField(Tag)
class Activity(models.Model):
actor_type = models.ForeignKey(ContentType, related_name='actor_type_activities')
actor_id = models.PositiveIntegerField()
actor = GenericForeignKey('actor_type', 'actor_id')
verb = models.CharField(max_length=10)
target_type = models.ForeignKey(ContentType, related_name='target_type_activities')
target_id = models.PositiveIntegerField()
target = GenericForeignKey('target_type', 'target_id')
tags = models.ManyToManyField(Tag)
Now, this would give the following list:
following_user = userA.relationship.follows_user.all()
following_user
[<Relationship: userB>, <Relationship: userC>]
following_tag = userA.relationship.follows_tag.all()
following_tag
[<Tag: tag-job>, <Tag: tag-finance>]
To filter I tried this way:
Activity.objects.filter(Q(actor__in=following_user) | Q(tags__in=following_tag))
But since actor is a GenericForeignKey I am getting an error:
FieldError: Field 'actor' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.
How can I filter the activities that will be unique, with the list of users and list of tags that the user is following? To be specific, how will I filter GenericForeignKey with the list of the objects to get the activities of the following users.
You should just filter by ids.
First get ids of objects you want to filter on
following_user = userA.relationship.follows_user.all().values_list('id', flat=True)
following_tag = userA.relationship.follows_tag.all()
Also you will need to filter on actor_type. It can be done like this for example.
actor_type = ContentType.objects.get_for_model(userA.__class__)
Or as #Todor suggested in comments. Because get_for_model accepts both model class and model instance
actor_type = ContentType.objects.get_for_model(userA)
And than you can just filter like this.
Activity.objects.filter(Q(actor_id__in=following_user, actor_type=actor_type) | Q(tags__in=following_tag))
What the docs are suggesting is not a bad thing.
The problem is that when you are creating Activities you are using auth.User as an actor, therefore you can't add GenericRelation to auth.User (well maybe you can by monkey-patching it, but that's not a good idea).
So what you can do?
#Sardorbek Imomaliev solution is very good, and you can make it even better if you put all this logic into a custom QuerySet class. (the idea is to achieve DRY-ness and reausability)
class ActivityQuerySet(models.QuerySet):
def for_user(self, user):
return self.filter(
models.Q(
actor_type=ContentType.objects.get_for_model(user),
actor_id__in=user.relationship.follows_user.values_list('id', flat=True)
)|models.Q(
tags__in=user.relationship.follows_tag.all()
)
)
class Activity(models.Model):
#..
objects = ActivityQuerySet.as_manager()
#usage
user_feed = Activity.objects.for_user(request.user)
but is there anything else?
1. Do you really need GenericForeignKey for actor? I don't know your business logic, so probably you do, but using just a regular FK for actor (just like for the tags) will make it possible to do staff like actor__in=users_following.
2. Did you check if there isn't an app for that? One example for a package already solving your problem is django-activity-steam check on it.
3. IF you don't use auth.User as an actor you can do exactly what the docs suggest -> adding a GenericRelation field. In fact, your Relationship class is suitable for this purpose, but I would really rename it to something like UserProfile or at least UserRelation. Consider we have renamed Relation to UserProfile and we create new Activities using userprofile instead. The idea is:
class UserProfile(models.Model):
user = AutoOneToOneField('auth.user')
follows_user = models.ManyToManyField('UserProfile', related_name='followed_by')
follows_tag = models.ManyToManyField(Tag)
activies_as_actor = GenericRelation('Activity',
content_type_field='actor_type',
object_id_field='actor_id',
related_query_name='userprofile'
)
class ActivityQuerySet(models.QuerySet):
def for_userprofile(self, userprofile):
return self.filter(
models.Q(
userprofile__in=userprofile.follows_user.all()
)|models.Q(
tags__in=userprofile.relationship.follows_tag.all()
)
)
class Activity(models.Model):
#..
objects = ActivityQuerySet.as_manager()
#usage
#1st when you create activity use UserProfile
Activity.objects.create(actor=request.user.userprofile, ...)
#2nd when you fetch.
#Check how `for_userprofile` is implemented this time
Activity.objects.for_userprofile(request.user.userprofile)
As stated in the documentation:
Due to the way GenericForeignKey is implemented, you cannot use such fields directly with filters (filter() and exclude(), for example) via the database API. Because a GenericForeignKey isn’t a normal field object, these examples will not work:
You could follow what the error message is telling you, I think you'll have to add a GenericRelation relation to do that. I do not have experience doing that, and I'd have to study it but...
Personally I think this solution is too complex to what you're trying to achieve. If only the user model can follow a tag or authors, why not include a ManyToManyField on it. It would be something like this:
class Person(models.Model):
user = models.ForeignKey(User)
follow_tag = models.ManyToManyField('Tag')
follow_author = models.ManyToManyField('Author')
You could query all followed tag activities per Person like this:
Activity.objects.filter(tags__in=person.follow_tag.all())
And you could search 'persons' following a tag like this:
Person.objects.filter(follow_tag__in=[<tag_ids>])
The same would apply to authors and you could use querysets to do OR, AND, etc.. on your queries.
If you want more models to be able to follow a tag or author, say a System, maybe you could create a Following model that does the same thing Person is doing and then you could add a ForeignKey to Follow both in Person and System
Note that I'm using this Person to meet this recomendation.
You can query seperately for both usrs and tags and then combine them both to get what you are looking for. Please do something like below and let me know if this works..
usrs = Activity.objects.filter(actor__in=following_user)
tags = Activity.objects.filter(tags__in=following_tag)
result = usrs | tags
You can use annotate to join the two primary keys as a single string then use that to filter your queryset.
from django.db.models import Value, TextField
from django.db.models.functions import Concat
following_actor = [
# actor_type, actor
(1, 100),
(2, 102),
]
searchable_keys = [str(at) + "__" + str(actor) for at, actor in following_actor]
result = MultiKey.objects.annotate(key=Concat('actor_type', Value('__'), 'actor_id',
output_field=TextField()))\
.filter(Q(key__in=searchable_keys) | Q(tags__in=following_tag))