Django search fields in multiple models - python

I want to search multiple fields in many models. I don't want to use other apps like 'Haystack', only pure Django. For example:
# models.py
class Person(models.Model):
first_name = models.CharField("First name", max_length=255)
last_name = models.CharField("Last name", max_length=255)
# other fields
class Restaurant(models.Model):
restaurant_name = models.CharField("Restaurant name", max_length=255)
# other fields
class Pizza(models.Model):
pizza_name = models.CharField("Pizza name", max_length=255)
# other fields
When I type "Tonny" I should get a:
"Tonny Montana" from Person model
"Tonny's Restaurant" from Restaurant model
"Tonny's Special Pizza" from Pizza model.

One solution is to query all the models
# Look up Q objects for combining different fields in a single query
from django.db.models import Q
people = Person.objects.filter(Q(first_name__contains=query) | Q(last_name__contains=query)
restaurants = Restaurant.objects.filter(restaurant_name__contains=query)
pizzas = Pizza.objects.filter(pizza_name__contains=query)
Then combine the results, if you want
from itertools import chain
results = chain(people, restaurants, pizzas)
Ok, sure, here's a more generic solution. Search all CharFields in all models:
search_models = [] # Add your models here, in any way you find best.
search_results = []
for model in search_models:
fields = [x for x in model._meta.fields if isinstance(x, django.db.models.CharField)]
search_queries = [Q(**{x.name + "__contains" : search_query}) for x in fields]
q_object = Q()
for query in search_queries:
q_object = q_object | query
results = model.objects.filter(q_object)
search_results.append(results)
This will give you a list of all the querysets, you can then mold it to a format you choose to work with.
To get a list of models to fill search_models, you can do it manually, or use something like get_models. Read the docs for more information on how that works.

Related

DRF: data structure in serializer or view?

Given the models below, I've been trying to figure out how to return the data structure I have in mind (also below) using Django REST Framework.
How would this be accomplished within a serializer, or does such a data structure need to be built within a view using traditional Django-style queries?
About
Basically, a word is created, users submit definitions for that word, and vote on each definition (funniest, saddest, wtf, etc.)
models.py
from django.db import models
class Word(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
word = models.CharField()
timestamp = models.DateTimeField()
class Definition(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
word = models.ForeignKey(Word, on_delete=models.CASCADE)
definition = models.CharField()
timestamp = models.DateTimeField()
class Vote_category(models.Model):
category = models.CharField()
class Vote_history(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
definition = models.ForeignKey(Definition, on_delete=models.CASCADE)
timestamp = models.DateTimeField()
vote = models.ForeignKey(Vote_category, on_delete=models.CASCADE)
Expected Query Result Structure
word: 'hello',
definitions: [
{
user: 'alice',
definition: 'an expression of greeting',
votes: {
funny: 3,
sad: 1,
wtf: 7
},
votes_total: 11
},
etc...
]
Thanks!
The schema you attached can (and should) be generated using Django REST Framework Serializers; the nested elements of your schema can be generated using nested serializers. Generally these serializers will inherit from the ModelSerializer.
Here is an example of the nested serializers you would use to begin to construct your schema:
class WordSerializer(serializers.ModelSerializer):
"""Serializer for a Word"""
definitions = DefinitionSerializer(many=True)
class Meta:
model = Word
fields = ('word', 'definitions')
class DefinitionSerializer(serializers.ModelSerializer):
"""Serializer for a Definition"""
user = UserSerializer(read_only=True)
votes = VoteSerializer(many=True)
class Meta:
model = Word
fields = ('definition', 'user', 'votes')
One part of the schema you have listed which may be more complicated is the map of vote category to vote count. DRF naturally would create a structure which is a list of objects rather than a single object as your schema has. To override that behavior you could look into creating a custom ListSerializer.

Searching by related fields in django admin

I've been looking at the docs for search_fields in django admin in the attempt to allow searching of related fields.
So, here are some of my models.
# models.py
class Team(models.Model):
name = models.CharField(max_length=255)
class AgeGroup(models.Model):
group = models.CharField(max_length=255)
class Runner(models.Model):
"""
Model for the runner holding a course record.
"""
name = models.CharField(max_length=100)
agegroup = models.ForeignKey(AgeGroup)
team = models.ForeignKey(Team, blank=True, null=True)
class Result(models.Model):
"""
Model for the results of records.
"""
runner = models.ForeignKey(Runner)
year = models.IntegerField(_("Year"))
time = models.CharField(_("Time"), max_length=8)
class YearRecord(models.Model):
"""
Model for storing the course records of a year.
"""
result = models.ForeignKey(Result)
year = models.IntegerField()
What I'd like is for the YearRecord admin to be able to search for the team which a runner belongs to. However as soon as I attempt to add the Runner FK relationship to the search fields I get an error on searches; TypeError: Related Field got invalid lookup: icontains
So, here is the admin setup where I'd like to be able to search through the relationships. I'm sure this matches the docs, but am I misunderstanding something here? Can this be resolved & the result__runner be extended to the team field of the Runner model?
# admin.py
class YearRecordAdmin(admin.ModelAdmin):
model = YearRecord
list_display = ('result', 'get_agegroup', 'get_team', 'year')
search_fields = ['result__runner', 'year']
def get_team(self, obj):
return obj.result.runner.team
get_team.short_description = _("Team")
def get_agegroup(self, obj):
return obj.result.runner.agegroup
get_agegroup.short_description = _("Age group")
The documentation reads:
These fields should be some kind of text field, such as CharField or TextField.
so you should use 'result__runner__team__name'.

Get all teachers that have at least 1 subject on table - Django Query Set

I Would like to get all the Teachers that have at least 1 subject. Currently I'm using...
user = users.objects.all().order_by('-karma')[:100]
Because people who does not have any subjects related is a Student.
Here is my models.py
class subjects(models.Model):
id_user = models.IntegerField(db_column='ID_user') # Field name made lowercase.
s = models.CharField(max_length=90)
def __unicode__(self):
return self.s
class Meta:
db_table = 'subjects'
class users(models.Model):
email = models.EmailField(max_length=160)
nick = models.CharField(unique=True, max_length=60)
karma = models.IntegerField(max_length=11)
pass_field = models.CharField(db_column='pass', max_length=160)
One option is to do this in two steps:
get id_user list from subjects model with the help of values_list():
user_ids = subjects.objects.values_list('id_user', flat=True).distinct()
get all users by the list of id_users using __in:
print users.objects.filter(pk__in=user_ids)
Also, since models are not related, you can make a raw query that would do the same in one go.

Include intermediary (through model) in responses in Django Rest Framework

I have a question about dealing with m2m / through models and their presentation in django rest framework. Let's take a classic example:
models.py:
from django.db import models
class Member(models.Model):
name = models.CharField(max_length = 20)
groups = models.ManyToManyField('Group', through = 'Membership')
class Group(models.Model):
name = models.CharField(max_length = 20)
class Membership(models.Model):
member = models.ForeignKey('Member')
group = models.ForeignKey('Group')
join_date = models.DateTimeField()
serializers.py:
imports...
class MemberSerializer(ModelSerializer):
class Meta:
model = Member
class GroupSerializer(ModelSerializer):
class Meta:
model = Group
views.py:
imports...
class MemberViewSet(ModelViewSet):
queryset = Member.objects.all()
serializer_class = MemberSerializer
class GroupViewSet(ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
When GETing an instance of Member, I successfully receive all of the member's fields and also its groups - however I only get the groups' details, without extra details that comes from the Membership model.
In other words I expect to receive:
{
'id' : 2,
'name' : 'some member',
'groups' : [
{
'id' : 55,
'name' : 'group 1'
'join_date' : 34151564
},
{
'id' : 56,
'name' : 'group 2'
'join_date' : 11200299
}
]
}
Note the join_date.
I have tried oh so many solutions, including of course Django Rest-Framework official page about it and no one seems to give a proper plain answer about it - what do I need to do to include these extra fields? I found it more straight-forward with django-tastypie but had some other problems and prefer rest-framework.
How about.....
On your MemberSerializer, define a field on it like:
groups = MembershipSerializer(source='membership_set', many=True)
and then on your membership serializer you can create this:
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field(source='group.id')
name = serializers.Field(source='group.name')
class Meta:
model = Membership
fields = ('id', 'name', 'join_date', )
That has the overall effect of creating a serialized value, groups, that has as its source the membership you want, and then it uses a custom serializer to pull out the bits you want to display.
EDIT: as commented by #bryanph, serializers.field was renamed to serializers.ReadOnlyField in DRF 3.0, so this should read:
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField(source='group.id')
name = serializers.ReadOnlyField(source='group.name')
class Meta:
model = Membership
fields = ('id', 'name', 'join_date', )
for any modern implementations
I was facing this problem and my solution (using DRF 3.6) was to use SerializerMethodField on the object and explicitly query the Membership table like so:
class MembershipSerializer(serializers.ModelSerializer):
"""Used as a nested serializer by MemberSerializer"""
class Meta:
model = Membership
fields = ('id','group','join_date')
class MemberSerializer(serializers.ModelSerializer):
groups = serializers.SerializerMethodField()
class Meta:
model = Member
fields = ('id','name','groups')
def get_groups(self, obj):
"obj is a Member instance. Returns list of dicts"""
qset = Membership.objects.filter(member=obj)
return [MembershipSerializer(m).data for m in qset]
This will return a list of dicts for the groups key where each dict is serialized from the MembershipSerializer. To make it writable, you can define your own create/update method inside the MemberSerializer where you iterate over the input data and explicitly create or update Membership model instances.
I just had the same problem and I ended it up solving it with an annotation on the group queryset.
from django.db.models import F
class MemberSerializer(ModelSerializer):
groups = serializers.SerializerMethodField()
class Meta:
model = Member
def get_groups(self, instance):
groups = instance.groups.all().annotate(join_date=F(membership__join_date))
return GroupSerializer(groups, many=True).data
class GroupSerializer(ModelSerializer):
join_date = serializers.CharField(required=False) # so the serializer still works without annotation
class Meta:
model = Group
fields = ..., 'join_date']
NOTE: As a Software Engineer, I love to use Architectures and I have deeply worked on Layered Approach for Development so I am gonna be Answering it with Respect to Tiers.
As i understood the Issue, Here's the Solution
models.py
class Member(models.Model):
member_id = models.AutoField(primary_key=True)
member_name = models.CharField(max_length =
class Group(models.Model):
group_id = models.AutoField(primary_key=True)
group_name = models.CharField(max_length = 20)
fk_member_id = models.ForeignKey('Member', models.DO_NOTHING,
db_column='fk_member_id', blank=True, null=True)
class Membership(models.Model):
membershipid = models.AutoField(primary_key=True)
fk_group_id = models.ForeignKey('Group', models.DO_NOTHING,
db_column='fk_member_id', blank=True, null=True)
join_date = models.DateTimeField()
serializers.py
import serializer
class AllSerializer(serializer.Serializer):
group_id = serializer.IntegerField()
group_name = serializer.CharField(max_length = 20)
join_date = serializer.DateTimeField()
CustomModels.py
imports...
class AllDataModel():
group_id = ""
group_name = ""
join_date = ""
BusinessLogic.py
imports ....
class getdata(memberid):
alldataDict = {}
dto = []
Member = models.Members.objects.get(member_id=memberid) #or use filter for Name
alldataDict["MemberId"] = Member.member_id
alldataDict["MemberName"] = Member.member_name
Groups = models.Group.objects.filter(fk_member_id=Member)
for item in Groups:
Custommodel = CustomModels.AllDataModel()
Custommodel.group_id = item.group_id
Custommodel.group_name = item.group_name
Membership = models.Membership.objects.get(fk_group_id=item.group_id)
Custommodel.join_date = Membership.join_date
dto.append(Custommodel)
serializer = AllSerializer(dto,many=True)
alldataDict.update(serializer.data)
return alldataDict
You would technically, have to pass the Request to DataAccessLayer which would return the Filtered Objects from Data Access Layer but as I have to Answer the Question in a Fast Manner so i adjusted the Code in Business Logic Layer!

Filtering a QuerySet With another QuerySet

Hi i'm not very good at English but i'll try to explain myself the best i could. I'm using python and Django to create a web project.
I have this 4 models (this is the best translation i can do of the tables and fields):
class Humans (models.Model):
name = models.CharField(max_length=15)
surname = models.CharField(max_length=15)
doc_num = models.CharField(max_length=11)
...
class Records (models.Model):
closing_state = models.CharField(max_length=2)
...
humans = models.ManyToManyField(Humans, through='Reco_Huma')
class Reco_Huma (models.Model):
id_record = models.ForeignKey(Records)
id_human = models.ForeignKey(Humans)
categorys = models.CharField(max_length=2)
reserv_identity = models.CharField(max_length=2)
repre_entity = models.CharField(max_length=2)
class Observations (models.Model):
id_record = models.ForeignKey(Records)
text = models.CharField(max_length=80)
category = models.CharField(max_length=2, choices=CAT)
Now given a doc_num from Humans, a text from Observations i want to get a QuerySet Of all the Records.
To clarify i first do this:
q1 = Reco_Huma.objects.filter(id_human.doc_num=x)
q2 = Observations.objects.filter(text=y)
both query-sets give me a list of id_record and then i want to connive that lists and filter the Records table with that id_record's
I hope you can understand me
Thanks in advance
To rephrase your query, you want all the Records associated with a certain Human and which have a certain Observation. So it should be:
result = Records.objects.filter(observations__text=y, humans__doc_num=x)
As a general rule, if you want to end up with a certain type of object, it helps to start from there in your query.

Categories