Django & Mongoengine get data from embedded document? - python

I am having trouble retrieving data from the embedded document in mongoengine.
models.py
from mongoengine import Document, EmbeddedDocument, fields
class ProductFields(EmbeddedDocument):
key_name = fields.StringField(required=True)
value = fields.DynamicField(required=True)
class Product(Document):
name = fields.StringField(required=True)
description = fields.StringField(required=True, null=True)
fields = fields.ListField(fields.EmbeddedDocumentField(ProductFields))
views.py
class ProductListView(APIView):
def get(self, request):
# list_products = Product.objects.all()
result=[]
productfields = ProductFields
for product in Product.objects:
data={
"name":product.name,
"description":product.description,
# "key":product.fields.key_name,
# "value":ProductFields.value,
}
print (data)
# print(productfields.key_name)
result.append(data)
return Response({"products":result,"message":"list of products.","requestStatus":1},status=status.HTTP_200_OK)
Output:
{
"description": "test description",
"name": "product1"
"fields":[
{ "key_name" : value},
{ "key_name" : value},
]
}
How do I get the above-desired output? Print function doesn't work because mongoengine returns object and not the value.

Here I see you're using APIView from Django Rest FrameWork. Have a look at django-rest-framework-mongoengine. If you're already familiar with DRF, you can use this extension to create your API endpoints with MongoDB easily.
You must have found some workaround by now even though you can mark this answer as correct so that if anyone else runs into the same problem in future, they can get the solution.

Related

DRF queryset to return specific field

I'm creating a django rest framework application with this structure (assuming imports are correct, so I omit them from the code below.
models.py:
class Door(models.Model):
type = models.CharField(max_length=40)
color = models.CharField(max_length=40)
serializers.py:
class DoorSerializer(serializers.ModelSerializer):
class Meta:
model = Door
fields = ['type', 'color']
views.py:
class DoorViewSet(viewsets.ModelViewSet):
serializer_class = DoorSerializer
queryset = Door.objects.all()
def get_queryset(self, *args, **kwargs):
queryset = Door.objects.all()
parameter = self.request.query_params.get('type', '')
if parameter:
return queryset.filter(type=parameter)
else:
return queryset
So far this behaves as intended, when I make an api call to localhost/Doors it lists all the doors. And when I make an api call to localhost/Doors/?type=big it lists all the doors that have the value "big" in their "type" field.
The addition I would like to make is another parameter check which would return a list of all the unique door types that exist in the database. This can be achieved in the manage.py shell by using: Door.objects.all().values('type').distinct()
My attempt was the following modifications to views.py:
...
parameter = self.request.query.params.get('type', '')
unique = self.request.query.params.get('unique', '')
if parameter:
...
elif unique:
return Door.objects.all().values('type').distinct()
...
My assumption was that this would return the same as Door.objects.all().values('type').distinct() when I make a call to localhost/Doors/?unique=whatever
However I am getting the error: "Got KeyError when attempting to get a value for field color on serializer DoorSerializer.\nThe serializer field might be named incorrectly and not match any attribute or key on the dict instance.\nOriginal exception text was: 'color'."
I assume this means that the serializer expects an object or a list of objects that contains all the fields of the corresponding model.
Is there some way I could circumvent this by fixing the view or should I create a different serializer? In either case, since I've gotten pretty confused with DRF / django differences and it is possible I won't be able to follow abstract instructions, could you provide a code solution that addresses the issue? Also, in the very likely case that my assumption is completely off, could you also explain what is causing the problem? Thank you for your time!
Edit for clarifying the desired result:
Assuming my database has 4 doors which are:
{
"id": 1,
"type": "big",
"color": "blue"
},
{
"id": 2,
"type": "big",
"color": "yellow"
},
{
"id": 3,
"type": "small",
"color": "green"
},
{
"id": 4,
"type": "big",
"color": "red"
},
I would like to make a get request to some url, for instance localhost/Doors/?unique=Yes and have the api return to me the list {"big", "small}
WRITING YOUR OWN VIEW: Short view that returns the list of type. You need to set up a new path here. I'd personally go for this option as the response you expect is way different to what the rest of your view does.
from rest_framework.decorators import api_view
from rest_framework.response import Response
#api_view()
def Unique_door_types(request):
types = Door.objects.values_list('type', flat=True).distinct()
return Response({"types": list(types)})
WITHOUT AN ADDITIONAL VIEW:
No need for additional view or serializer. Override the list method. Note that this is closer to a trick than to a good way of programming.
from rest_framework.response import Response
class DoorViewSet(viewsets.ModelViewSet):
serializer_class = DoorSerializer
def get_queryset(self, *args, **kwargs):
queryset = Door.objects.all()
parameter = self.request.query_params.get('type', '')
if parameter:
return queryset.filter(type=parameter)
else:
return queryset
def list(self, request):
unique = self.request.query_params.get('unique', '')
if unique:
types = Door.objects.values_list('type', flat=True).distinct()
return Response({"types": list(types)})
return super().list()
My suggestion would be to create a separate route like /doors/types/. You do this by adding a method to your DoorViewSet class with a #action decorator. See https://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing for more details about how to do this.

Filter Embedded Document List in GraphQL

I'm building a GraphQL application in Python/Graphene using a MongoDB backend (through MongoEngine). Everything has been working well, but noticed that there's not a lot documentation for handling nested lists of embedded documents. I thought one power of GraphQL was the ability to project only the properties you want, but it doesn't appear to be the case fully.
Looking at this collection as an example:
[
{
"name": "John Doe",
"age": 37,
"preferences": [
{
"key": "colour",
"value": "Green"
},
{
"key": "smell",
"value": "onions cooking in butter"
},
...
]
},
...
]
If I want to find a particular object through GraphQL, I would look up through a query like
{
person(name: "John Doe"){edges{node{
name age preferences{edges{node{
key value
}}}
}}}
}
But this could bring back hundreds of nested documents. What I would like to do instead is to identify the requested nested documents as part of the projection request.
{
person(name: "John Doe"){edges{node{
name age preferences(key: "colour"){edges{node{
key value
}}}
}}}
}
My understanding reading the GraphQL spec is these sub-queries are not possible, but wanted to confirm with experts first. And if it is possible, how would I implement it to support these types of requests?
Update Maybe a schema example will provide some more insightful responses.
class PreferenceModel(mongoengine.EmbeddedDocument):
key = mongoengine.fields.StringField()
value = mongoengine.fields.StringField()
class Preference(graphene_mongo.MongoengineObjectType):
class Meta:
interfaces = (graphene.relay.Node, )
model = PreferenceModel
class PersonModel(mongoengine.Document):
meta = {'collection': 'persons'}
name = mongoengine.fields.StringField()
age = mongoengine.fields.IntField()
preferences = mongoengine.fields.EmbeddedDocumentListField(PreferenceModel)
class Person(graphene_mongo.MongoengineObjectType):
class Meta:
interfaces = (graphene.relay.Node, )
model = PersonModel
class Query(graphene.ObjectType):
person = graphene_mongo.MongoengineConnectionField(Person)
schema = graphene.Schema(query=Query, types=[Person])
app = starlette.graphql.GraphQLApp(schema=schema)
Using this above structure, what changes would be necessary to allow for queries/filters on nested objects?
I had a similiar issue but working graphene-django. I solved it using custom resolvers on the DjangoObjectType, like this:
import graphene
from graphene_django import DjangoObjectType
from .models import Question, Choice, SubChoice
class SubChoiceType(DjangoObjectType):
class Meta:
model = SubChoice
fields = "__all__"
class ChoiceType(DjangoObjectType):
sub_choices = graphene.List(SubChoiceType, search_sub_choices=graphene.String())
class Meta:
model = Choice
fields = ("id", "choice_text", "question")
def resolve_sub_choices(self, info, search_sub_choices=None):
if search_sub_choices:
return self.subchoice_set.filter(sub_choice_text__icontains=search_sub_choices)
return self.subchoice_set.all()
class QuestionType(DjangoObjectType):
choices = graphene.List(ChoiceType, search_choices=graphene.String())
class Meta:
model = Question
fields = ("id", "question_text")
def resolve_choices(self, info, search_choices=None):
if search_choices:
return self.choice_set.filter(choice_text__icontains=search_choices)
return self.choice_set.all()
class Query(graphene.ObjectType):
all_questions = graphene.List(QuestionType, search_text=graphene.String())
all_choices = graphene.List(ChoiceType, search_text=graphene.String())
all_sub_choices = graphene.List(SubChoiceType)
def resolve_all_questions(self, info, search_text=None):
qs = Question.objects.all()
if search_text:
qs = qs.filter(question_text__icontains=search_text)
return qs
def resolve_all_choices(self, info, search_text=None):
qs = Choice.objects.all()
if search_text:
qs = qs.filter(choice_text__icontains=search_text)
return qs
def resolve_all_sub_choices(self, info):
qs = SubChoice.objects.all()
return qs
schema = graphene.Schema(query=Query)
you can find the example here: https://github.com/allangz/graphene_subfilters/blob/main/mock_site/polls/schema.py
It may work for you

Django-graphene: how to filter with an OR operator

I am pretty new with both Django and Graphene, and couldn't get around a problem which might be fairly simple, but I had no luck with the docs or google to get an answer.
Let's say I have the following model:
class Law(models.Model):
year = models.IntegerField(default=None)
number = models.IntegerField(default=None)
description = TextField(default=None)
body = models.TextField(default=None)
And the following schema:
class LawType(DjangoObjectType):
class Meta:
model = models.Law
filter_fields = {
"year": ["exact"],
"number": ["exact"],
"description": ["contains"],
"body": ["icontains"],
}
interfaces = (graphene.Node, )
class Query(graphene.AbstractType):
all_laws = DjangoFilterConnectionField(LawType)
def resolve_all_laws(self, args, context, info):
return models.Law.objects.all()
How do I make a query or define a FilterSet class so that it will return a list of objects such that a word is found in the description or in the body?
{
allLaws(description_Icontains: "criminal", body_Icontains: "criminal") {
edges{
node{
year
number
}
}
}
}
I couldn't find an answer in the graphene-django documentation nor in the django-filter documentation.
Any clues? Thanks in advance
You can do this with the base Django framework by using a Q object: https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q-objects
For instance, this statement would yield a single Q object representing the OR of the two queries:
Q(description__icontains='criminal') | Q(body__icontains='criminal')
You can pass this statement into a filter query:
Law.objects.filter(
Q(description__icontains='criminal') | Q(body__icontains='criminal')
)

Django Rest Framework receive primary key value in POST and return model object as nested serializer

I'm not completely sure that the title of my question is as specific as I wanted it to be, but this is the case:
I have a HyperlinkedModelSerializer that looks like this:
class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
carpool = SchoolBuildingCarpoolSerializer()
class Meta:
model = ParentArrival
As you can see the carpool is defined as a nested serializer object and what I want is to be able to make a POST request to create a ParentArrival in this way (data as application/json):
{
...
"carpool": "http://localhost:8000/api/school-building-carpools/10/"
...
}
And receive the data in this way:
{
"carpool": {
"url": "http://localhost:8000/api/school-building-carpools/10/"
"name": "Name of the carpool",
...
}
}
Basically, I'm looking for a way to deal with nested serializers without having to send data as an object (but id or url in this case) in POST request, but receiving the object as nested in the serialized response.
I have been happy with my previous solution, but decided to look again and I think I have another solution that does exactly what you want.
Basically, you need to create your own custom field, and just overwrite the to_representation method:
class CarpoolField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
pk = super(CarpoolField, self).to_representation(value)
try:
item = ParentArrival.objects.get(pk=pk)
serializer = CarpoolSerializer(item)
return serializer.data
except ParentArrival.DoesNotExist:
return None
def get_choices(self, cutoff=None):
queryset = self.get_queryset()
if queryset is None:
return {}
return OrderedDict([(item.id, str(item)) for item in queryset])
class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
carpool = CarpoolField(queryset=Carpool.objects.all())
class Meta:
model = ParentArrival
This will allow you to post with
{
"carpool": 10
}
and get:
{
"carpool": {
"url": "http://localhost:8000/api/school-building-carpools/10/"
"name": "Name of the carpool",
...
}
}
It's simple.
As you know, Django appends "_id" to the field name in the ModelClass, and you can achieve it in the SerializerClass, and the original filed can also be achieved. All you have to do is like this
class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
# ...
carpool_id = serializers.IntegerField(write_only=True)
carpool = SchoolBuildingCarpoolSerializer(read_only=True)
# ...
class Meta:
fields = ('carpool_id', 'carpool', ...)
And use carpool_id in POST request.
How about overriding the to_representation method?
class YourSerializer(serializers.ModelSerializer):
class Meta:
model = ModelClass
fields = ["id", "foreignkey"]
def to_representation(self, instance):
data = super(YourSerializer, self).to_representation(instance)
data['foreignkey'] = YourNestedSerializer(instance.foreignkey).data
return data
One way to do it is to keep 'carpool' as the default you get from DRF, and then add a read-only field for the nested object.
Something like this (I don't have time to test the code, so consider this pseudo-code. If you cannot get it to work, let me know, and will spend more time):
class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
carpool_info = serializers.SerializerMethodField(read_only=True)
class Meta:
model = ParentArrival
fields = ('id', 'carpool', 'carpool_info',)
def get_carpool_info(self, obj):
carpool = obj.carpool
serializer = SchoolBuildingCarpoolSerializer(carpool)
return serializer.data
If your only nested object is carpool, I would also suggest switching to the regular ModelSerializer so carpool only shows the ID (10) and the nested object then can show the URL.
class ParentArrivalSerializer(serializers.ModelSerializer):
....
and then if it all works, you will be able to do a post with
{
"carpool": 10
}
and your get:
{
"carpool": 10
"carpool_info": {
"url": "http://localhost:8000/api/school-building-carpools/10/"
"name": "Name of the carpool",
...
}
}
I have never found another solution, so this is the trick I have used several times.

How can one customize Django Rest Framework serializers output?

I have a Django model that is like this:
class WindowsMacAddress(models.Model):
address = models.TextField(unique=True)
mapping = models.ForeignKey('imaging.WindowsMapping', related_name='macAddresses')
And two serializers, defined as:
class WindowsFlatMacAddressSerializer(serializers.Serializer):
address = serializers.Field()
class WindowsCompleteMappingSerializer(serializers.Serializer):
id = serializers.Field()
macAddresses = WindowsFlatMacAddressSerializer(many=True)
clientId = serializers.Field()
When accessing the serializer over a view, I get the following output:
[
{
"id": 1,
"macAddresses": [
{
"address": "aa:aa:aa:aa:aa:aa"
},
{
"address": "bb:bb:bb:bb:bb:bb"
}
],
"clientId": null
}
]
Almost good, except that I'd prefer to have:
[
{
"id": 1,
"macAddresses": [
"aa:aa:aa:aa:aa:aa",
"bb:bb:bb:bb:bb:bb"
],
"clientId": null
}
]
How can I achieve that ?
Create a custom serializer field and implement to_native so that it returns the list you want.
If you use the source="*" technique then something like this might work:
class CustomField(Field):
def to_native(self, obj):
return obj.macAddresses.all()
I hope that helps.
Update for djangorestframework>=3.9.1
According to documentation, now you need override either one or both of the to_representation() and to_internal_value() methods. Example
class CustomField(Field):
def to_representation(self, value)
return {'id': value.id, 'name': value.name}
Carlton's answer will work do the job just fine. There's also a couple of other approaches you could take.
You can also use SlugRelatedField, which represents the relationship, using a given field on the target.
So for example...
class WindowsCompleteMappingSerializer(serializers.Serializer):
id = serializers.Field()
macAddresses = serializers.SlugRelatedField(slug_field='address', many=True, read_only=True)
clientId = serializers.Field()
Alternatively, if the __str__ of the WindowsMacAddress simply displays the address, then you could simply use RelatedField, which is a basic read-only field that will give you a simple string representation of the relationship target.
# models.py
class WindowsMacAddress(models.Model):
address = models.TextField(unique=True)
mapping = models.ForeignKey('imaging.WindowsMapping', related_name='macAddresses')
def __str__(self):
return self.address
# serializers.py
class WindowsCompleteMappingSerializer(serializers.Serializer):
id = serializers.Field()
macAddresses = serializers.RelatedField(many=True)
clientId = serializers.Field()
Take a look through the documentation on serializer fields to get a better idea of the various ways you can represent relationships in your API.

Categories

Resources