DRF formatting XLSX content - django

I am trying to set a different color on every second row in XLSX file. From the documentation I see that I can pass some conditions using body property or get_body() method, but this only allows me to set somewhat "static" conditions. Here is the ViewSet config responsible for rendering the XLSX file:
class MyViewSet(XLSXFileMixin, ModelViewSet):
def get_renderers(self) -> List[BaseRenderer]:
if self.action == "export":
return [XLSXRenderer()]
else:
return super().get_renderers()
#action(methods=["GET"], detail=False)
def export(self, request: Request) -> Response:
serializer = self.get_serializer(self.get_queryset(), many=True)
return Response(serializer.data)
# Properties for XLSX
column_header = {
"titles": [
"Hostname", "Operating System", "OS name", "OS family", "OS version", "Domain", "Serial number",
"Available patches",
],
"tab_title": "Endpoints",
"style": {
"font": {
"size": 14,
"color": "FFFFFF",
},
"fill": {
"start_color": "3F803F",
"fill_type": "solid",
}
}
}
body = {
"style": {
"font": {
"size": 12,
"color": "FFFFFF"
},
"fill": {
"fill_type": "solid",
"start_color": "2B2B2B"
},
}
}

OK. I got the answer after some digging through the source code. The render method of XLSXRenderer has this piece of code:
for row in results:
column_count = 0
row_count += 1
flatten_row = self._flatten(row)
for column_name, value in flatten_row.items():
if column_name == "row_color":
continue
column_count += 1
cell = ws.cell(
row=row_count, column=column_count, value=value,
)
cell.style = body_style
ws.row_dimensions[row_count].height = body.get("height", 40)
if "row_color" in row:
last_letter = get_column_letter(column_count)
cell_range = ws[
"A{}".format(row_count): "{}{}".format(last_letter, row_count)
]
fill = PatternFill(fill_type="solid", start_color=row["row_color"])
for r in cell_range:
for c in r:
c.fill = fill
So when I added a field row_color in my serializer as SerializerMethodField I was able to define a function that colors rows:
def get_row_color(self, obj: Endpoint) -> str:
"""
This method returns color value for row in XLSX sheet.
(*self.instance,) extends queryset to a list (it must be a queryset, not a single Endpoint).
.index(obj) gets index of currently serialized object in that list.
As the last step one out of two values from the list is chosen using modulo 2 operation on the index.
"""
return ["353535", "2B2B2B"][(*self.instance,).index(obj) % 2]

Related

list indices must be integers or slices when appending a dict to a serializer in django

I have an already created serializer object, I am trying to add a new object to the serializer but I keep getting the error
list indices must be integers or slices, not str
I am not able to trace where I am going wrong with the creation of the new object. Here is my code below and more explanations.
class ClusterFunctionView(generics.ListAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = FunctionListSerializer
def get_queryset(self):
//returns serializer
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
user = self.request.user
cluster = Cluster.objects.filter(user_id=user.id, id=self.kwargs["cluster_id"]).first()
schedule = Schedule.objects.filter(clusters__in=[cluster]).values().first() # I am getting the new object here
print('schedule', type(schedule)) # I checked the type, it is a dict
response.data['schedule'] = schedule # doesn't seem to be appending to the existing serializer.
return response
The following is an example of the output of the schedule object, I printed using print('schedule', schedule):
schedule {'id': 7, 'user_id': 3, 'creation_time': datetime.datetime(2020, 5, 25, 15, 44, 39, 875485), 'name': 'mandard_1', 'is_active': True, 'comment': 'extract mardard premier batch', 'cron_expression': '#once'}
A sample of the existing serializer on which I should add the above object is:
[
{
"id": 1,
"function": "connections",
"max_concurrency": 1,
"mandatory_params": {},
"public_params": {
"cluster": {
"account": true,
"max_pages": {
"max": 100,
"default": 100
},
"profiles_per_page": {
"max": 25,
"default": 25
}
}
},
"params": {
"max_pages": 100,
"account_function": "user_account",
"alchemy_directory": "connections",
"unique_result_obj_attribute": "connection_id"
}
}
]
I am expecting a serializer with a schedules object, a result like :
[
{
"id": 1,
"function": "connections",
"max_concurrency": 1,
"mandatory_params": {},
"public_params": {
"cluster": {
"account": true,
"max_pages": {
"max": 100,
"default": 100
},
"profiles_per_page": {
"max": 25,
"default": 25
}
}
},
"params": {
"max_pages": 100,
"account_function": "user_account",
"alchemy_directory": "connections",
"unique_result_obj_attribute": "connection_id"
}
},
"schedule": {} # this should be added as a result
]
What could the problem be, and what solution could I undertake? Thanks
I was able to solve the question, realizing it was a small mistake that I did.
In the def list function,
This line was the one causing the error :
response.data['schedule'] = schedule
I realised that the serializer produced was of a list result, and I was initially trying to append the schedule : {}, directly into a list, hence the error. It should be appended into the first object. Hence I needed to access the object in the list using index, so changing it to below solved the problem:
response.data[0]['schedule'] = schedule

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

How to stop Graphql + Django-Filters to return All Objects when Filter String is Empty?

Using:
Django 3.x [ Django-Filters 2.2.0, graphene-django 2.8.0, graphql-relay 2.0.1 ]
Vue 2.x [ Vue-Apollo ]
I have a simple Birds Django-Model with Fields like name, habitat and applied different Filters on these Fields like icontains or iexact. My Goal was to apply a simple search field in my Frontend (Vue). So far it works, but whenever this Filter Value is empty or has blanks (see Example 3), Graphql returns all Objects.
My first approach was on the FrontEnd and to use some kind of logic on my input value, like when String is Empty/blank send isnull=true . But then i thought that Django should handle this in the first place.
I guess this Issue relates to my filters (see Django < relay_schema.py) or in other words do i have to apply some kind of logic on these Filters?
In the moment i try to customize some filterset_class but it feels like this would maybe too much, maybe i missed something? So i ask here if maybe someone has some hints, therefore my question is:
How to stop Graphql + Django-Filters to return All Objects when Filter String is Empty?
GraphiQL IDE
Example 1
query {birdsNodeFilter (name_Iexact: "finch") {
edges {
node {
id
name
}
}
}
}
Returns
{
"data": {
"birdsNodeFilter": {
"edges": [
{
"node": {
"id": "QmlyZHNOb2RlOjE=",
"name": "Finch",
"habitat": "Europe"
}
}
]
}
}
}
Fine for me!
Example 2
query {birdsNodeFilter (name_Iexact: "Unicorns") {
edges {
node {
id
name
habitat
}
}
}
}
Returns
{
"data": {
"birdsNodeFilter": {
"edges": []
}
}
}
No Unicorns there - good
Example 3
query {birdsNodeFilter (name_Iexact: "") {
edges {
node {
id
name
}
}
}
}
Return
{
"data": {
"birdsNodeFilter": {
"edges": [
{
"node": {
"id": "QmlyZHNOb2RlOjE=",
"name": "Finch",
"habitat": "Europe"
}
},
{
"node": {
"id": "QmlyZHNOb2RlOjI=",
"name": "Bald Eagle",
"habitat": "USA"
}
},
<...And so on...>
Not fine for me!
Django
relay_schema.py
class BirdsNode(DjangoObjectType):
class Meta:
model = Birds
filter_fields = {
'id': ['iexact'],
'name': ['iexact', 'icontains', 'istartswith', 'isnull'],
'habitat': ['iexact', 'icontains', 'istartswith'],
}
interfaces = (relay.Node, )
class BirdQuery(graphene.ObjectType):
birdConNode = relay.Node.Field(BirdsNode)
birdsNodeFilter = DjangoFilterConnectionField(BirdsNode)
This is my Solution which worked in GraphiQL and in my Frontend VUE.
I added a logic to the Birds2Query with def resolve_all_birds2 for each Filter (for testing purpose not on all Filter ) .
Besides that i also added a ExtendedConnection for counting.
Note: i changed the class names from my former Question.
Update: This Solution works on the python side. But the apollo client provides also the Apollo manager - also known as Dollar Apollo - there you can also use the this.$apollo.queries.tags.skip property as an static or dynamic solution to start and stop queries.
relay_schema.py
class ExtendedConnection(Connection):
class Meta:
abstract = True
total_count = Int()
edge_count = Int()
name_check = ""
def resolve_total_count(root, info, **kwargs):
return root.length
def resolve_edge_count(root, info, **kwargs):
return len(root.edges)
class Birds2Node(DjangoObjectType):
class Meta:
model = Birds
filter_fields = {
'id': ['exact', 'icontains'],
'name': ['exact', 'icontains', 'istartswith', 'iendswith'],
}
interfaces = (relay.Node, )
connection_class = ExtendedConnection
class Birds2Query(ObjectType):
birds2 = relay.Node.Field(Birds2Node)
all_birds2 = DjangoFilterConnectionField(Birds2Node)
def resolve_all_birds2(self, info, **kwargs):
# Filtering for Empty/ Blank Values in Filter.Key.Value before returning queryset
if 'name__icontains' in kwargs:
nameIcon = kwargs['name__icontains']
nameIconBool = bool(nameIcon.strip()) # if blanks turns False
if nameIconBool == False: # has blanks
return Birds.objects.filter(name=None)
pass
if 'name__istartswith' in kwargs:
nameIsta = kwargs['name__istartswith']
nameIstaBool = bool(nameIsta.strip()) # if blanks turns False
if nameIstaBool == False: # has blanks
return Birds.objects.filter(name=None)
pass
return
GraphiQL
Blockquote
Example 1
query {allBirds2 (name_Icontains:""){
totalCount
edgeCount
edges {
node {
id
name
habitat
}
}
}
}
Stopped to return all Objects while Filter is blank.
{
"data": {
"allBirds2": {
"totalCount": 0,
"edgeCount": 0,
"edges": []
}
}
}
Example 2 with blanks and one letter
query {allBirds2 (name_Icontains:" f "){
totalCount
edgeCount
edges {
node {
id
name
habitat
}
}
}
}
return - exactly what i wanted
{
"data": {
"allBirds2": {
"totalCount": 1,
"edgeCount": 1,
"edges": [
{
"node": {
"id": "QmlyZHMyTm9kZTox",
"name": "Finch",
"habitat": "Europe"
}
}
]
}
}
}

How to send multiple objects through HttpResponse or JsonResponse in django

I have two objects influencer_data and user_list in my views function.I want to send both influencer_data and user_list through the HttpResponse method and obtain the data in Json format.
My views function is:
def index(request):
influencers = Influencer.objects.all()
influencer_data = serializers.serialize("json",influencers)
user_list = UserList.objects.all()
user_list = serializers.serialize("json",user_list)
context = {
'influencer_data':influencer_data,
'user_list':user_list,
}
return HttpResponse(influencer_data,user_list, content_type='application/json')
When I pass both influencer_data and user_list I get the error
__init__() got multiple values for argument 'content_type'
When I change the return HttpResponse statement to
return HttpResponse(context, content_type='application/json')
I get
influencer_datauser_list
i.e just the key values from the dictionary
When I change the return statement to
return HttpResponse(json.dumps(context), content_type='application/json')
I get the output as:
"influencer_data": "[{\"model\": \"influencer_listings.influencer\", \"pk\": 8794, \"fields\": {\"full_name\": \"F A I Z S H A I K H \\ud83c\\udf08\", \"username\": \"mr_faizzz_07\", \"photo\": \"\", \"email_id\": \"\", \"external_url\": \"\", \"location_city\": \"Mumbai\", \"categories\": \"\", \"hashtags\": \"['#foryou', '#blessyou', '#all', '#faizanshaikh', '#keepsmiling', '#blessed', '#look',
(The Json object becomes a string)
When I pass only one object i.e either influencer_data or user_list. I get a Json object i.e it works correctly(I want data in the given format)
[
{
"model": "influencer_listings.influencer",
"pk": 8794,
"fields": {
"full_name": "F A I Z S H A I K H 🌈",
"username": "mr_faizzz_07",
"photo": "",
"email_id": "",
"external_url": "",
"location_city": "Mumbai",
"categories": "",
"hashtags": "['#foryou', '#blessyou', '#all', '#faizanshaikh', '#keepsmiling', '#blessed', '#look', '#ramzan', '#loveyou', '#lover', '#cuteboys', '#keepgoing', '#picoftheday', '#feathers', '#brothers', '#faizshaikhhhh', '#pictures', '#jummahmubarak', '#lovers']",
How should I deal with this?
def index(request):
influencers = Influencer.objects.all().values()
user_list = UserList.objects.all().values()
context = {
'influencer_data': influencer_data,
'user_list': user_list,
}
data = json.dumps(context, indent=4, sort_keys=True, default=str)
return HttpResponse(data, content_type='application/json')

Django - Multiple data values in fusion pie charts

I want to add multiple data values in my pie charts taking model fields. currently i am doing this:
def detail(request, pk):
if not request.user.is_authenticated():
return render(request, 'registration/login.html')
else:
bowler = Bowlers.objects.get(pk=pk)
select_list = UserSelect.objects.filter(user=request.user, bowler=pk)
all_select = UserSelect.objects.filter(user=request.user).count()
team = Team.objects.filter(user=request.user)
dataSource = {}
dataSource['chart'] = {
"caption": "last year",
"subCaption": "Harry's SuperMart",
"xAxisName": "Month",
"yAxisName": "Revenues (In USD)",
"numberPrefix": "$",
"theme": "zune",
"type": "doughnut2d"
}
dataSource['data'] = []
# Iterate through the data in `Revenue` model and insert in to the `dataSource['data']` list.
data = {}
data['label'] = bowler.name
data['value'] = bowler.ave
dataSource['data'].append(data)
column2D = FusionCharts("doughnut2d", "ex1", "600", "350", "chart-1", "json", dataSource)
context = {
'bowler': bowler,
'select_list': select_list,
'all_select': all_select,
'team': team,
'output': column2D.render()
}
return render(request, 'bowler_detail.html', context)
and i want to add more values something like this: (this is JSON format of adding multiple data values)
"data": [
{
"label": "Venezuela",
"value": "290"
},
{
"label": "Saudi",
"value": "260"
},
]
Any guide how will I add more values fetching from django models in my chart?