From post man, I'm trying to make POST request to an order API created using django restframework but I'm getting the following error:
product = Product.objects.get(id=i['product'])
TypeError: string indices must be integers
The specific point where the error is located is specified in the error but I find difficulty constructing VALID json for the request body.
Here is how I'm making the request on postman:
{
"orderItems":{
"product": {"name":"abc", "brand":"def", "image"www.example.com/img", "description":"xyz", "price":"50"},
"qty":"2",
"price":"200"
},
"shippingAddress": {
"address":"abc", "city":"B", "postalCode":"12345",
},
"paymentMethod":"monify",
"itemPrice":"100"
}
Here is the program:
class Product(models.Model):
category = models.CharField(max_length=50, choices=Categories.choices, default=Categories.medications)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name="user_product", null=True)
name = models.CharField(max_length=150)
brand = models.CharField(max_length=255, default="brand")
productClass = models.CharField(max_length=50, null=True, blank=True)
image = models.ImageField(upload_to="images/products/")
label = models.CharField(max_length=254, default='', blank=True, null=True)
price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)
stock = models.IntegerField(null=True, blank=True, default=0)
dateCreated = models.DateTimeField(auto_now_add=True)
class Order(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name="user_order", null=True)
paymentMethod = models.CharField(max_length=200, null=True, blank=True)
dateCreated = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.dateCreated)
models.py
class OrderItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
image = models.CharField(max_length=200, null=True, blank=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True)
name = models.CharField(max_length=200, null=True, blank=True)
qty = models.IntegerField(null=True, blank=True, default=0)
price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)
def __str__(self):
return str(self.name)
The view I', trying to test is shown below:
def addOrderItems(request):
user = request.user
data = request.data
orderItems = data['orderItems']
if orderItems and len(orderItems) == 0:
return Response({'detail': 'Order item was not provided'}, status=status.HTTP_400_BAD_REQUEST)
else:
# Step 1: Create order
order = Order.objects.create(
user=user,
paymentMethod=data['paymentMethod'],
)
#Step 2: Create shipping address
shipping = ShippingAddress.objects.create(
order=order,
address=data['shippingAddress']['address'],
)
# Step 3: Create order items, then set order to orderItem relationship
for i in orderItems:
product = Product.objects.get(id=i['product'])
item = OrderItem.objects.create(
product=product,
order=order,
name=product.name,
qty=i['qty'],
price=i['price'],
image=product.image.url,
)
# Step 4: Update stock
product.countInStock -= item.qty
product.save()
serializer = OrderSerializer(order, many=False)
return Response(serializer.data)
Any hint on this is well appreciated
I think the payload is not correct. Now the whole product data is uploaded, but it should be changed into the product_id. Because in the view, it is being considered as the id field like Product.objects.get(id=i['product']).
So the solution is like the following.
First, the payload should be changed.
{
"orderItems":[{
"product_id": 3, // here I changed
"qty":"2",
"price":"200"
}],
"shippingAddress": {
"address":"abc", "city":"B", "postalCode":"12345",
},
"paymentMethod":"monify",
"itemPrice":"100"
}
And in the addOrderItems function,
def addOrderItems(request):
...
if orderItems and len(orderItems) == 0:
...
else:
...
# Step 3: Create order items, then set order to orderItem relationship
for i in orderItems:
product = Product.objects.get(id=i['product_id'])
...
Your JSON is invalid (the "image" key in "orderItems" was messed up). To check this, you can use an online tool like https://jsonformatter.curiousconcept.com/.
Moreover, from the way you are iterating over it, orderItems should contain a list.
Here is the corrected JSON:
{
"orderItems": [{
"product": {"name":"abc", "brand":"def", "image": "www.example.com/img", "description":"xyz", "price":"50"},
"qty":"2",
"price":"200"
}],
"shippingAddress": {
"address":"abc", "city":"B", "postalCode":"12345",
},
"paymentMethod":"monify",
"itemPrice":"100"
}
In your Product model, you should also add an ID as PrimaryKey to be able to retrieve it later in your view:
import uuid
class Product(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# ... (all your other attributes)
The JSON will then include this product ID:
{
"orderItems": [{
"product": {"id": "9b84820b-1b4f-43a2-b576-14c3e1635906", "name":"abc", "brand":"def", "image": "www.example.com/img", "description":"xyz", "price":"50"},
"qty":"2",
"price":"200"
}],
"shippingAddress": {
"address":"abc", "city":"B", "postalCode":"12345",
},
"paymentMethod":"monify",
"itemPrice":"100"
}
Then the line product = Product.objects.get(id=i['product']) should be replaced with:
product = Product.objects.get(id=i['product']['id'])
Related
What I am working on is saving a new listing that is created by a given user on my commerce site, and displaying/redirecting the user to my index page. For some reason, the view keeps returning None and I'm not exactly sure why. Here is the code snippets below:
views.py
def createListing(request):
if request.method == "POST":
listing = NewListingForm(request.POST)
if listing.is_valid():
creator = request.user
title = listing.cleaned_data['title']
price = listing.cleaned_data['price']
description = listing.cleaned_data['description']
image = listing.cleaned_data['image']
category = listing.cleaned_data['category']
# Using .objects.create much simpler solution
auction = Listing.objects.create(
creator=creator,
title=title,
description=description,
price=price,
category=category,
image=image,
)
starting_bid = auction.price
bid = Bid.objects.create(
bid=starting_bid,
user=creator,
auction=auction
)
return render(request, "auctions/index.html", {
"message": "Listing Created Successfully."
})
if request.method == "GET":
return render(request, "auctions/create.html", {
"create_form": NewListingForm()
})
models.py
class User(AbstractUser):
pass
class Comment(models.Model):
comment = models.CharField(max_length=64)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_comment")
class Listing(models.Model):
CATEGORIES = [
('Toys', 'Toys'),
('Electronics', 'Electronics'),
('Lifestyle', 'Lifestyle'),
('Home', 'Home'),
('Fashion', 'Fashion'),
('Other', 'Other')
]
creator = models.ForeignKey(User, on_delete=models.CASCADE, related_name="creator")
title = models.CharField(max_length=64, blank=False, null=False)
price = models.DecimalField(max_digits=10, decimal_places=2, blank=False, null=True)
description = models.CharField(blank=True, max_length=1064, null=True)
category = models.CharField(max_length=64, blank=True, choices=CATEGORIES)
image = models.URLField(default='https://user-images.githubusercontent.com/52632898/161646398-6d49eca9-267f-4eab-a5a7-6ba6069d21df.png')
starting_bid = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
bid_counter = models.IntegerField(default=0)
active = models.BooleanField(default=True)
winner = models.CharField(max_length=64, blank=True, null=True)
def _str__(self):
return f"{self.title} by {self.creator}"
class Bid(models.Model):
bid = models.DecimalField(decimal_places=2, max_digits=10)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_bid")
date_created = models.DateTimeField(auto_now=True)
auction = models.ForeignKey(Listing, on_delete=models.CASCADE)
def __str__(self):
return f"{self.bid} made by {self.user}"
The new listing form:
# Creating a new listing form
class NewListingForm(forms.Form):
title = forms.CharField(label='', min_length=2, widget=forms.TextInput(
attrs={"class": "form-control", "style": "margin-bottom: 10px", "placeholder": "Title"}))
description = forms.CharField(label='', widget=forms.Textarea(
attrs={"class": "form-control", "style": "margin-bottom: 10px", "placeholder": "Description"}))
price = forms.DecimalField(label='', widget=forms.NumberInput(
attrs={"class": "form-control", "style": "margin-bottom: 10px", "placeholder": "Starting Bid ($)"}))
image = forms.ImageField(label="Choose an Image for your Listing")
category = forms.MultipleChoiceField(
label='Pick a Category', widget=forms.CheckboxSelectMultiple, choices=Listing.CATEGORIES)
I have tried looking into urls.py to ensure I was calling the right view with its according name and using 'return redirect('index')' but it doesn't seem to work either. I'm relatively new to django so any help would be appreciated! Let me know if there are any other files that are required to help clarify the problem.
Django handled http GET method automatically not needing to write it, and also need to remove the unwanted stuff.
your code becomes like this...
from django.shortcuts import render,redirect
from django.contrib import messages
def createListing(request):
listing =NewListingForm()
if request.method == "POST":
listing = NewListingForm(request.POST)
if listing.is_valid():
creator = request.user
title = listing.cleaned_data['title']
price = listing.cleaned_data['price']
description = listing.cleaned_data['description']
image = listing.cleaned_data['image']
category = listing.cleaned_data['category']
# Using .objects.create much simpler solution
auction = Listing.objects.create(
creator=creator,
title=title,
description=description,
price=price,
category=category,
image=image,
)
starting_bid = auction.price
bid = Bid.objects.create(
bid=starting_bid,
user=creator,
auction=auction
)
messages.success(request,"Listing Created Successfully")
return redirect("/home/")
context = {
"listing": listing
}
return render(request, "auctions/create.html", context)
In my app, each user has his wallets in which he can save his daily expenses.
How can I get sum of money for each instance of wallet with many expenses saved to it?
I've tried serializers.SerializerMethodField()
from django.db.models import Sum
from django.db.models import Q
class WalletInstanceSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
entry = BudgetEntrySerializer(many=True, read_only=True)
expense_sum = serializers.SerializerMethodField()
class Meta:
model = WalletInstance
fields = '__all__'
def get_expense_sum(self, obj):
return WalletInstance.objects.filter(Q(id=obj.id)|Q(entry__entry_type='income')).aggregate(Sum('entry__amount'))['entry__amount__sum']
And this gives me data in the correct format but the sum is wrong, for example
[
{
"id": "d458196e-49f1-42db-8bc2-ee1dba438953",
"owner": 1,
"entry": [
{
"id": 3,
"owner": 1,
"title": "dsfdsf",
"amount": 7,
"description": "sdfdsf",
"entry_type": "income",
"date_added": "2022-08-13",
"entry_category": {
"id": 2,
"name": "Transport"
}
},
{
"id": 4,
"owner": 1,
"title": "fesfvsdfgvbtdg",
"amount": 12,
"description": "efesf",
"entry_type": "income",
"date_added": "2022-08-13",
"entry_category": {
"id": 2,
"name": "Transport"
}
}
],
"viewable": [
"admin#gmail.com"
],
"expense_sum": 83,
"name": "dsdsds",
"date_added": "2022-08-13"
}
]
Expense_sum is 83 but amount fields are 7 and 12 so that's equal to 19
How can i get the correct number?
Models
class BudgetEntry(models.Model):
STATE= [
('income','income'),
('expenses','expenses'),
]
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owner_of_entry', on_delete=models.CASCADE)
title = models.CharField(max_length=20)
amount = models.IntegerField()
description = models.CharField(max_length=60, null=True)
entry_type = models.CharField(max_length=15, choices=STATE, null=True)
entry_category = models.ForeignKey(Category, null=True, blank=True, related_name='category_of_entry', on_delete=models.SET_NULL)
date_added = models.DateField(auto_now_add=True)
class WalletInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=30, null=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owner', on_delete=models.CASCADE)
viewable = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='can_view', blank=True)
entry = models.ManyToManyField(BudgetEntry, related_name='BudgetEntry', blank=True)
date_added = models.DateField(auto_now_add=True)
I think ORM query for the BudgetEntry is wrong. It should be like the following:
def get_expense_sum(self, obj):
return obj.entry.filter(entry_type='income').aggregate(Sum('entry__amount'))['entry__amount__sum']
I've figured it out. Firstly I've created #property in my database to get sum of expenses, but this wasn't very useful If I ever wanted to count income so I passed the value arg to the function where the value can be expense/income
class WalletInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=30, null=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owner', on_delete=models.CASCADE)
viewable = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='can_view', blank=True)
entry = models.ManyToManyField(BudgetEntry, related_name='BudgetEntry', blank=True)
date_added = models.DateField(auto_now_add=True)
def total_amount(self, value):
queryset = self.entry.filter(entry_type=value).aggregate(
total_amount=models.Sum('amount'))
return queryset["total_amount"]
And now I'm calling this function in my serializer instead of counting there
class WalletInstanceSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
entry = BudgetEntrySerializer(many=True, read_only=True)
viewable = serializers.SlugRelatedField(many=True, queryset=get_user_model().objects.all(), slug_field='email')
expense_sum = serializers.SerializerMethodField()
class Meta:
model = WalletInstance
fields = '__all__'
def get_expense_sum(self, obj):
wallet_obj = WalletInstance.objects.get(id=obj.id)
return wallet_obj.total_amount('expenses')
I am doing tutorials online and trying to make mutation works on graphql but I kept on getting errors which I have no idea what where the real error comes from and how to start debugging where I have done wrong.
looking at this youtube for mutation
https://www.youtube.com/watch?v=aB6c7UUMrPo&t=1962s
and the graphene documentation http://docs.graphene-python.org/en/latest/types/mutations/
I noticed that because of different graphene version, that is why I have reading the documentation instead of following exactly as the youtube
I got things setup but then couldn't get it to work, when I execute the mutation query I get error.
I have a model like this.
class Product(models.Model):
sku = models.CharField(max_length=13, help_text="Enter Product Stock Keeping Unit", null=True, blank=True)
barcode = models.CharField(max_length=13, help_text="Enter Product Barcode (ISBN, UPC ...)", null=True, blank=True)
title = models.CharField(max_length=200, help_text="Enter Product Title", null=True, blank=True)
description = models.TextField(help_text="Enter Product Description", null=True, blank=True)
unitCost = models.FloatField(help_text="Enter Product Unit Cost", null=True, blank=True)
unit = models.CharField(max_length=10, help_text="Enter Product Unit ", null=True, blank=True)
quantity = models.FloatField(help_text="Enter Product Quantity", null=True, blank=True)
minQuantity = models.FloatField(help_text="Enter Product Min Quantity", null=True, blank=True)
family = models.ForeignKey('Family', null=True, blank=True)
location = models.ForeignKey('Location', null=True, blank=True)
def __str__(self):
return self.title
I have this for my Product schema
class ProductType(DjangoObjectType):
class Meta:
model = Product
filter_fields = {'description': ['icontains']}
interfaces = (graphene.relay.Node,)
class CreateProduct(graphene.Mutation):
class Argument:
barcode = graphene.String()
# form_errors = graphene.String()
product = graphene.Field(lambda: ProductType)
def mutate(self, info, barcode):
product = Product(barcode=barcode)
return CreateProduct(product=product)
class ProductMutation(graphene.AbstractType):
create_product = CreateProduct.Field()
class ProductQuery(object):
product = relay.Node.Field(ProductType)
all_products = DjangoFilterConnectionField(ProductType)
def resolve_all_products(self, info, **kwargs):
return Product.objects.all()
global schema looks like this
class Mutation(ProductMutation,
graphene.ObjectType):
pass
class Query(FamilyQuery,
LocationQuery,
ProductQuery,
TransactionQuery,
graphene.ObjectType):
# This class extends all abstract apps level Queries and graphene.ObjectType
pass
allGraphQLSchema = graphene.Schema(query=Query, mutation=Mutation)
as for trying out the querying...this is my query
mutation ProductMutation {
createProduct(barcode:"abc"){
product {
id, unit, description
}
}
}
error returned
{
"errors": [
{
"message": "Unknown argument \"barcode\" on field \"createProduct\" of type \"Mutation\".",
"locations": [
{
"column": 17,
"line": 2
}
]
}
]
}
Can someone please give me a hand on what I should try and do?
Thanks in advance for any help
Figured my own problem.
There are three things, which are Argument should be Arguments and under the mutate function, I should use a regular django create model so from product = Product(barcode=barcode) into product = Product.objects.create(barcode=barcode) last but not least class ProductMutation(graphene.AbstractType): should be class ProductMutation(graphene.ObjectType):
so the code should be
class ProductType(DjangoObjectType):
class Meta:
model = Product
filter_fields = {'description': ['icontains']}
interfaces = (graphene.relay.Node,)
class CreateProduct(graphene.Mutation):
class Arguments: # change here
barcode = graphene.String()
product = graphene.Field(lambda: ProductType)
def mutate(self, info, barcode):
# change here
# somehow the graphene documentation just state the code I had in my question which doesn't work for me. But this one does
product = Product.objects.create(barcode=barcode)
return CreateProduct(product=product)
class ProductMutation(graphene.ObjectType): # change here
create_product = CreateProduct.Field()
class ProductQuery(object):
product = relay.Node.Field(ProductType)
all_products = DjangoFilterConnectionField(ProductType)
def resolve_all_products(self, info, **kwargs):
return Product.objects.all()
So if I have the following model class in Django:
class Person(models.Model):
name = models.CharField(max_length=250)
is_active = models.BooleanField(default=True)
address_line1 = models.CharField(max_length=100, blank=True, null=True)
address_line2 = models.CharField(max_length=100, blank=True, null=True)
town = models.CharField(max_length=100, blank=True, null=True)
county = models.CharField(max_length=100, blank=True, null=True)
post_code = models.CharField(max_length=100, blank=True, null=True)
and my goal is to serialize it into the following JSON:
{
"name": "Joe Bloggs",
"is_active": true,
"contact": {
"address1": "Bloggs House"
"address2": "1 Bloggs Lane",
"city": "Bloggs Town",
"county": "Bloggs Town",
"zip": "BL0 GG5",
"country": "UK"
}
}
I tried the following, but it didn't work and I'm pretty sure that's not how the serializers.ListField is meant to work (I think it's meant to be for a list of the same thing):
class MailChimpListSerializer(serializers.ModelSerializer):
class Meta:
model = Person
contact = serializers.DictField(
address1=serializers.CharField(source='address_line1'),
address2=serializers.CharField(source='address_line2'),
city=serializers.CharField(source='town'),
state=serializers.CharField(source='town', read_only=True),
zip=serializers.CharField(source='post_code'),
country=serializers.SerializerMethodField()
)
permission_reminder = serializers.SerializerMethodField()
campaign_defaults = serializers.DictField(
from_name=serializers.CharField(source='name'),
from_email=serializers.CharField(source='primary_contact_email'),
subject=serializers.CharField(),
language=serializers.CharField()
)
email_type_option = serializers.SerializerMethodField()
fields = ('name', 'contact', 'permission_reminder',
'campaign_defaults', 'email_type_option')
How do I create the contact JSON list with the address etc in it?
What you want is a DictField not a ListField, the key contact in your desired JSON output is an object (dict in Python), not a list:
contact = serializers.DictField(
address1=serializers.CharField(source='address_line1'),
address2=serializers.CharField(source='address_line2'),
...
)
Here's another way that is more manual:
class MySerializer(serializers.ModelSerializer):
contact = serializers.SerializerMethodField()
def get_contact(self, obj):
return dict(
address1=obj.address1, # As long as the fields are auto serializable to JSON
some_field=SomeSerializer(obj.some_field).data,
)
My models:
class Product(models.Model):
name = models.CharField(max_length=125, blank=False, null=False)
description = models.CharField(max_length=255, blank=True)
abbreviation = models.CharField(max_length=15, blank=True)
category = models.ForeignKey('inventory.Category',
on_delete=models.SET_NULL, null=True, blank=True)
metric = models.CharField(max_length=15, blank=True, null=True)
class Category(models.Model):
name = models.CharField(max_length=55, blank=False, null=False)
abbreviation = models.CharField(max_length=15, blank=True)
parent_category = models.ForeignKey('inventory.Category',
on_delete=models.SET_NULL, null=True, blank=True)
I want to recover all products:
products = Product.objects.all()
That's ok. I get all the products with success.
But I want that the products returned came with the ForeignKey attribute as an textual field from the referenced model, and no the raw integer that references it.
The field in question would be the Category.name.
At present my return is:
{model: product, pk:1, fields: {..., category: 1} }, { ....... } ...
What I Want:
{model: product, pk:1, fields: {..., category: 'Categoria 1 '}
I need the set pre-populated with the related model info, cause I'll return a JSON string as result for the service caller.
My attempt to return JSON:
products = Product.objects.all()
serialized_products = serializers.serialize('json', products)
return JsonResponse({'result': 'success', 'data':serialized_products}) #return all
How can I do this? The most simple and elegant form...
The only thing I can think of is use values_list to return whatever fields you want:
products = Product.objects.all().values('name',
'description',
'category__name',
'category__id',
'category__abbreviation')
return JsonResponse(products)
It might be a little less convenient then serializer, but I don't think serializer is good at handling foreigne key relations because what if you have deeper relationships that you need to include in the result?