django rest framework - get two models - django

I have multiple models that are associated by Foreign Keys. I can export them all separately using django rest framework on a one to one basis, and I can also export multiple ones nested. However I want to be able to essentially "concatenate" them together into a single json/xml export.
The models in the example below are joined by a one to one foreign key on jobdtl_id. I have some where it's one to many but I'm hoping I can figure that out when I know how to get a view that will link 2 separate models like I want below -
Here's an example of what I want the json to look like by hitting a single URL like
http://localhost/job/4/
{
"job": {
"-id": "9878",
"-name": "This is the job",
"-master": "blahserver",
"-dbversion": "234",
"-xmlversion": "1",
"jobmst": {
"jobmst_id": "9878",
"jobmst_type": "2",
"jobmst_prntid": "234",
"jobmst_active": "Y",
"jobmst_name": "This is the job",
"jobmst_owner": "Owner",
"jobdtl_id": "9878",
"jobmst_lstchgtm": {
"-date": "Y",
"#text": "2013-10-23 09:22:08.0"
},
"jobmst_prntname": "Parent",
"jobmst_alias": "9878"
},
"jobdtl": {
"jobdtl_id": "9878",
"jobdtl_cmd": "blah.exe",
"jobdtl_failalarm": "NULL",
"nodmst_id": "NULL",
"nodlstmst_id": "NULL",
"jobdtl_inhevent": "Y",
"jobdtl_inhoptions": "Y",
"jobdtl_inhagent": "Y",
"jobdtl_inhrepeat": "Y",
"jobdtl_inhtime": "Y",
"jobdtl_timewin": "NULL",
"jobdtl_saveoutput": "Y",
"jobdtl_outputname": "NULL",
"jobdtl_trackmethod": "1",
"jobdtl_trackcmd": "NULL",
"jobdtl_deplogic": "1",
"jobdtl_rerun": "NULL",
"jobdtl_params": "--blah --ok"
},
"jobdep": [
{
"jobdep_id": "79670",
"jobmst_id": "9878",
"jobdep_type": "1",
"jobdep_jobmst": "another job",
"varmst_id": "NULL"
},
{
"-num": "2",
"jobdep_id": "83783",
"jobmst_id": "9878",
"jobdep_type": "1",
"jobdep_jobmst": "and another",
"varmst_id": "NULL"
}
],
"trgjob": [
{
"trgjob_id": "22286",
"trgmst_id": "23455",
"jobmst_id": "9878"
},
{
"-num": "2",
"trgjob_id": "28980",
"trgmst_id": "23521",
"jobmst_id": "9878"
},
{
"-num": "3",
"trgjob_id": "28981",
"trgmst_id": "9237",
"jobmst_id": "9878"
}
]
}
}
The models are basically like this -
class Jobdtl(models.Model):
jobdtl_id = models.IntegerField(primary_key=True)
jobdtl_cmd = models.TextField(blank=True)
....
jobdtl_duration = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'jobdtl'
class Jobmst(models.Model):
jobmst_id = models.IntegerField(primary_key=True)
jobmst_type = models.SmallIntegerField()
....
jobdtl_id = models.ForeignKey('Jobdtl', db_column='jobdtl_id', related_name='mstdtl', blank=True, null=True)
def __unicode__(self):
return self.jobmst_name
class Meta:
managed = False
db_table = 'jobmst'
end caveat I'm converting the json from how the XML looks for the existing legacy app which is like so -
<?xml version="1.0"?>
<job id="9878" name="This is the job" master="blahserver" dbversion="532" xmlversion="1">
<jobmst>
<jobmst_id>9878</jobmst_id>
<jobmst_type>2</jobmst_type>
<jobmst_prntid>234</jobmst_prntid>
<jobmst_active>Y</jobmst_active>
<jobmst_name>This is the job</jobmst_name>
<jobmst_owner>Owner</jobmst_owner>
<jobdtl_id>9878</jobdtl_id>
<jobmst_lstchgtm date="Y">2013-10-23 09:22:08.0</jobmst_lstchgtm>
<jobmst_prntname>Parent</jobmst_prntname>
<jobmst_alias>9878</jobmst_alias>
</jobmst>
<jobdtl>
<jobdtl_id>9878</jobdtl_id>
<jobdtl_cmd>blah.exe</jobdtl_cmd>
<jobdtl_failalarm>NULL</jobdtl_failalarm>
<nodmst_id>NULL</nodmst_id>
<nodlstmst_id>NULL</nodlstmst_id>
<jobdtl_inhevent>Y</jobdtl_inhevent>
<jobdtl_inhoptions>Y</jobdtl_inhoptions>
<jobdtl_inhagent>Y</jobdtl_inhagent>
<jobdtl_inhrepeat>Y</jobdtl_inhrepeat>
<jobdtl_inhtime>Y</jobdtl_inhtime>
<jobdtl_timewin>NULL</jobdtl_timewin>
<jobdtl_saveoutput>Y</jobdtl_saveoutput>
<jobdtl_outputname>NULL</jobdtl_outputname>
<jobdtl_trackmethod>1</jobdtl_trackmethod>
<jobdtl_trackcmd>NULL</jobdtl_trackcmd>
<jobdtl_deplogic>1</jobdtl_deplogic>
<jobdtl_rerun>NULL</jobdtl_rerun>
<jobdtl_params>--blah --ok</jobdtl_params>
</jobdtl>
<jobdep>
<jobdep_id>79670</jobdep_id>
<jobmst_id>9878</jobmst_id>
<jobdep_type>1</jobdep_type>
<jobdep_jobmst>another job</jobdep_jobmst>
<varmst_id>NULL</varmst_id>
</jobdep>
<jobdep num="2">
<jobdep_id>83783</jobdep_id>
<jobmst_id>9878</jobmst_id>
<jobdep_type>1</jobdep_type>
<jobdep_jobmst>and another</jobdep_jobmst>
<varmst_id>NULL</varmst_id>
</jobdep>
<trgjob>
<trgjob_id>22286</trgjob_id>
<trgmst_id>23455</trgmst_id>
<jobmst_id>9878</jobmst_id>
</trgjob>
<trgjob num="2">
<trgjob_id>28980</trgjob_id>
<trgmst_id>23521</trgmst_id>
<jobmst_id>9878</jobmst_id>
</trgjob>
<trgjob num="3">
<trgjob_id>28981</trgjob_id>
<trgmst_id>9237</trgmst_id>
<jobmst_id>9878</jobmst_id>
</trgjob>
</job>

class MSTSerializer(serializers.HyperlinkedModelSerializer):
jobdtl_id = DTLSerializer()
class Meta:
model = Jobmst
fields = ('id', 'url', 'jobdtl_id'...)
class DTLSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Jobdtl
fields = ('id', 'url', ...)
Would result in a more correct data structure of
{
"jobmst_id": 4,
"jobmst_type": 1,
"jobdtl_id": {
"jobdtl_id": 4,
"jobdtl_cmd": null,
"jobdtl_duration": 1379
},
}

I found the solution by doing the following in my views.py
...
#csrf_exempt
def tesxml_test(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
mst = Jobmst.objects.using('database1').get(jobmst_id=pk)
dtl = Jobdtl.objects.using('database1').get(jobdtl_id=pk)
dep = Jobdep.objects.using('database2').filter(jobmst_id=pk).order_by('jobdep_id')
trg = Trgjob.objects.using('database1').filter(jobmst_id=pk).order_by('trgjob_order')
except Jobmst.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
#Get String Results of 4 queries
jobmststring = JobmstSerializer(mst)
jobdtlstring = JobdtlSerializer(dtl)
jobdepstring = JobdepSerializer(dep)
trgjobstring = TrgjobSerializer(trg)
#Get serialized Results of 4 queries
jobmst_serialized = {'jobmst': jobmststring.data}
jobdtl_serialized = {'jobdtl': jobdtlstring.data}
jobdep_serialized = {'jobdep': jobdepstring.data}
trgjob_serialized = {'trgjob': trgjobstring.data}
jobgroup = jobmst_serialized, jobdtl_serialized, jobdep_serialized, trgjob_serialized,
jobgroupresponse = TESXMLResponse(jobgroup)
return jobgroupresponse
...
It's not perfect but it puts me on the next step of my problem which is customizing the renderer to get the data in the root fields which I have another SO question for :)

Related

Django/Wagtail Rest Framework API Ordering/Pagination

I am using wagtail. I have serialized my API. I want to order them by -first_published_at, when someone hit my API url api/v2/pages they will see an ordered API without filtering it via URL. here is my api.py code:
class ProdPagesAPIViewSet(BaseAPIViewSet):
renderer_classes = [JSONRenderer]
filter_backends = [FieldsFilter,
ChildOfFilter,
AncestorOfFilter,
DescendantOfFilter,
OrderingFilter,
TranslationOfFilter,
LocaleFilter,
SearchFilter,]
meta_fields = ["type","seo_title","search_description","first_published_at"]
body_fields = ["id","type","seo_title","search_description","first_published_at","title"]
listing_default_fields = ["type","seo_title","search_description","first_published_at","id","title","alternative_title","news_slug","blog_image","video_thumbnail","categories","blog_authors","excerpt","content","content2","tags",]
nested_default_fields = []
ordered_queryset= []
name = "pages"
model = AddStory
api_router.register_endpoint("pages", ProdPagesAPIViewSet)
I have tried ordered_queryset= [AddStory.objects.order_by('-first_published_at')]
But it's not ordered by the newest published stories. How should I do the query?
Here is my API response
{
"meta": {
"total_count": 6
},
"items": [
{
"id": 4,
"meta": {
"type": "blog.AddStory",
"seo_title": "",
"search_description": "",
"first_published_at": "2022-08-30T11:05:11.341355Z"
},
{
"id": 6,
"meta": {
"type": "blog.AddStory",
"seo_title": "",
"search_description": "",
"first_published_at": "2022-08-30T11:13:47.114889Z"
},
{
"id": 7,
"meta": {
"type": "blog.AddStory",
"seo_title": "",
"search_description": "",
"first_published_at": "2022-08-31T11:13:47.114889Z"
},
Solved after using get_queryset instead of order_queryset
#api.py
class ProdPagesAPIViewSet(BaseAPIViewSet):
renderer_classes = [JSONRenderer]
filter_backends = [FieldsFilter,
ChildOfFilter,
AncestorOfFilter,
DescendantOfFilter,
OrderingFilter,
TranslationOfFilter,
LocaleFilter,
SearchFilter,]
meta_fields = ["type","seo_title","search_description","first_published_at"]
body_fields = ["id","type","seo_title","search_description","first_published_at","title"]
listing_default_fields = ["type","seo_title","search_description","first_published_at","id","title","alternative_title","news_slug","blog_image","video_thumbnail","categories","blog_authors","excerpt","content","content2","tags",]
nested_default_fields = []
def get_queryset(self):
return self.model.objects.all().order_by("-first_published_at")
name = "pages"
model = AddStory
api_router.register_endpoint("pages", ProdPagesAPIViewSet)

Django rest framework - serializer for dictionary structured payload

I'm trying to create a Serializer for a payload that looks something like this -
{
"2fd08845-9b21-4972-87ed-2e7fd03448c5": {
"operation": "Create",
"operationId": "356f6501-a117-4c8d-98ce-dcb4344d481b",
"user": "superuser",
"immediate": "true"
},
"fe6d0c85-0021-431e-9955-e8e1b1ebc414": {
"operation": "Create",
"operationId": "adcedb2f-c751-441f-8108-2c29667ea9cf",
"user": "employee",
"immediate": "false"
}
}
I thought of using DictField, but my problem is that there isn't a field name. it's only a dictionary of keys and values.
I tried something like:
class UserOperationSerializer(serializers.Serializer):
operation = serializers.ChoiceField(choices=["Create", "Delete"])
operationId = serializers.UUIDField()
user = serializers.CharField()
immediate = serializers.BooleanField()
class UserOperationsSerializer(serializers.Serializer):
test = serializers.DictField(child=RelationshipAuthorizeObjectSerializer())
But again, there isn't a 'test' field.
I think your easiest path forward would be to flatten the payload to the following format:
[
{
"request_id": "2fd08845-9b21-4972-87ed-2e7fd03448c5",
"operation": "Create",
"operationId": "356f6501-a117-4c8d-98ce-dcb4344d481b",
"user": "superuser",
"immediate": "true"
},
{
"request_id": "fe6d0c85-0021-431e-9955-e8e1b1ebc414",
"operation": "Create",
"operationId": "adcedb2f-c751-441f-8108-2c29667ea9cf",
"user": "employee",
"immediate": "false"
}
]
And then serialize it. Otherwise, you'd be creating custom fields/serializers which is not pretty.
The way I finally solved it was to add a dynamic 'body' field that contains the real payload of the request.
class UserOperationSerializer(serializers.Serializer):
operation = serializers.ChoiceField(choices=["Create", "Delete"])
operationId = serializers.UUIDField()
user = serializers.CharField()
immediate = serializers.BooleanField()
class UserOperationsSerializer(serializers.Serializer):
body = serializers.DictField(child=UserOperationSerializer())
def __init__(self, *args, **kwargs):
kwargs['data'] = {'body': kwargs['data']}
super().__init__(*args, **kwargs)
Then, in the View, I will use that data as serializer.validated_data['body']
That did the work for me.

How to return a function as a query result in graphene?

I have a post model, and I want to know if the currently logged in user has liked the post or not:
Would something like this work or should I use a different request to know if the user has liked/not liked the post:
class PostNode(DjangoObjectType):
id = graphene.ID(source='pk', required=True)
liked = graphene.Boolean(source=Like.objects.filter(user=info.context.user,post=Post.objects.get(pk=id)))
class Meta:
model = Post
interfaces = (graphene.relay.Node,)
(of course it doen't work)
The query:
query {
posts {
edges {
node {
liked
title
text
}
}
}
}
The result:
{
"data": {
"posts": {
"edges": [
{
"node": {
"liked": false,
"title": "hi",
"text": "m"
}
},
{
"node": {
"liked": true,
"title": "blalblala",
"text": "blalalblala"
}
}
]
}
}
}
You can use custom resolvers to achieve this behavior, like the following:
class PostNode(DjangoObjectType):
id = graphene.ID(required=True)
liked = graphene.Boolean()
class Meta:
model = Post
interfaces = (graphene.relay.Node,)
def resolve_liked(parent, info):
# "parent" here is the Post model instance
return Like.objects.exists(user=info.context.user, post=parent)

Django Rest Framework: Get unique list of values from nested structure

I want to be able to return a list of strings from a deeply nested structure of data. In this scenario, I have a API that manages a chain of bookstores with many locations in different regions.
Currently, I have an API endpoint that takes a region's ID and returns a nested JSON structure of details about the region, the individual bookstores, and the books that can be found in each store.
{
"region": [
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
},
{
"book": {
"name": "Baz"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Baz"
}
},
{
"book": {
"name": "Qux"
}
}
]
}
]
}
My models look like the following. I am aware these models don't make the most sense for this contrived example, but it does reflect my real world code:
class Book(TimeStampedModel):
name = models.CharField(default="", max_length=512)
class Bookstore(TimeStampedModel):
value = models.CharField(default="", max_length=1024)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
class Region(TimeStampedModel):
stores = models.ManyToManyField(Bookstore)
class BookstoreChain(TimeStampedModel):
regions = models.ManyToManyField(Region)
The serializers I created for the above response look like:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
class BookstoreSerializer(serializers.ModelSerializer):
books = BookSerializer()
class Meta:
model = Bookstore
fields = "__all__"
class RegionSerializer(serializers.ModelSerializer):
stores = BookstoreSerializer(many=True)
class Meta:
model = Region
fields = "__all__"
class BookstoreChainSerializer(serializers.ModelSerializer):
regions = RegionSerializer(many=True)
class Meta:
model = BookstoreChain
fields = "__all__"
I'm not sure what my view or serializer for this solution need to look like. I'm more familiar with writing raw SQL or using an ORM/Linq to get a set of results.
While the above response is certainty useful, what I really want is an API endpoint to return a unique list of book names that can be found in a given region (Foo, Bar, Baz, Qux). I would hope my response to look like:
{
"books": [
"Foo",
"Bar",
"Baz",
"Qux"
]
}
My feeble attempt so far has a urls.py with the following path:
path("api/regions/<int:pk>/uniqueBooks/", views.UniqueBooksForRegionView.as_view(), name="uniqueBooksForRegion")
My views.py looks like:
class UniqueBooksForRegionView(generics.RetrieveAPIView):
queryset = Regions.objects.all()
serializer_class = ???
So you start from region you have to get the stores, so you can filter the books in the stores, here is a solution which will work.
Note:
Avoid using .get() in *APIView because it will trigger an error if the request does not have the ID, you can use get_object_or_404(), but then you cannot log your error in Sentry.
To get an element from an *APIView, use filter().
import logging as L
class UniqueBooksForRegionView(generics.RetrieveAPIView):
lookup_field = 'pk'
def get(self, *args, **kwargs)
regions = Region.objects.filter(pk=self.kwargs[self.lookup_field])
if regions.exists():
region = regions.first()
stores_qs = region.stores.all()
books_qs = Book.objects.filter(store__in=stores_qs).distinct()
# use your book serializer
serializer = BookSerializer(books_qs, many=True)
return Response(serializer.data, HTTP_200_OK)
else:
L.error(f'Region with id {self.kwargs[self.lookup_field]} not found.')
return Response({'detail':f'Region with id {self.kwargs[self.lookup_field]} not found.'}, HTTP_404_NOT_FOUND)
Note
Here is the flow, the code may need some tweaks, but I hope it helps you understand the flow

Flatten json return by DRF

I have json API returned as below format.
But I want to return json API decomposing namingzone key as specified below.
Could anyone tell me how I can revise serializer to achieve this?
serializer.py is also specified below.
For models.py and views.py, please refer to my previous post.
current
{
"zone": {
"zone": "office_enclosed",
"namingzone": [
{
"naming": "moffice"
}
]
},
"lpd": 11.9,
"sensor": true
},
{
"zone": {
"zone": "office_open",
"namingzone": [
{
"naming": "off"
},
{
"naming": "office"
}
]
},
"lpd": 10.5,
"sensor": true
}
Target
{
"zone": "office_enclosed",
"naming": "moffice",
"lpd": 11.9,
"sensor": true
},
{
"zone": "office_open",
"naming": "off",
"lpd": 10.5,
"sensor": true
},
{
"zone": "office_open",
"naming": "office",
"lpd": 10.5,
"sensor": true
}
serializer.py
class namingNewSerializer(serializers.ModelSerializer):
class Meta:
model=Naming
fields=('naming',)
class zoneSerializer(serializers.ModelSerializer):
namingzone=namingNewSerializer(many=True)
class Meta:
model=Zone
fields = ('zone','namingzone')
class lightSerializer(serializers.ModelSerializer):
zone = zoneSerializer()
class Meta:
model=Light
fields = ('zone','lpd','sensor')
class namingSerializer(serializers.ModelSerializer):
zone=zoneSerializer()
class Meta:
model=Naming
fields=('zone','naming')
I would say using Serializer might complicate the implementations. Rather, you can take an pythonic approach. Try like this:
class SomeView(APIView):
...
def get(self, request, *args, **kwargs):
data = lightSerializer(Light.objects.all(), many=True).data
data = list(data) # convert lazy object to list
updated_data = list()
for item in data:
newdict = dict()
zone = item['zone']
newdict.update({'zone':zone['zone'], 'lpd': item['lpd'], 'sensor':item['sensor']})
for naming_zone in zone.get('namingzone'):
naming_zone.update(newDict)
updated_data.append(naming_zone)
return Response(updated_data, status=status.HTTP_200_OK)
See DRF Field document about source. It will help you.
https://www.django-rest-framework.org/api-guide/fields/#source