Django Rest Framework - get foreignkey id from value when inserting - django

I can not get a clear answer after two days of searching for what must probably be one of the most common things to do with a DRF:
I have the following model:
class ProcessedStockAmounts(models.Model):
prodName = models.ForeignKey(Productlist, on_delete=models.CASCADE, blank=False, unique=False)
amount = models.CharField(unique=False, max_length=255)
time = models.ForeignKey(StockTakingTimes, on_delete=models.CASCADE, blank=False, unique=False, default=1)
def __str__(self):
return str(self.prodName)
And I am returning a JSON object via my API that looks like this:
[{'prodName': 'SV1', 'amount': '1111111', 'time' : 1}]
When I insert my prodName with a value it has no problem, but obviously my user will not know the prodName ID and only the prod name. So when I try to insert the above I get the following error:
ValueError: Cannot assign "'SV1'": "ProcessedStockAmounts.prodName" must be a "Productlist" instance.
This was the closest I got to an answer and when I do the following it actually inserts:
p = ProcessedStockAmounts(amount='33', prodName = Productlist.objects.get(productid = 'SV1'), time = StockTakingTimes.objects.get(times='06:00'))
p.save()
but giving data this way is obviously defeating the purpose.
My serializer looks like the following:
class TestSerializer(serializers.ModelSerializer):
# time = serializers.SlugRelatedField(read_only=True, slug_field='time')
prodName = serializers.CharField()
# prodName = serializers.SlugRelatedField(read_only=True, slug_field='prodName')
class Meta:
model = ProcessedStockAmounts
fields = ('prodName','amount','time')
With my view:
class InsertMultiProcessedStock(APIView):
def post(self, request, format='json'):
serializer = TestSerializer(data = request.data, many=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors)
Productlist model:
class Productlist(models.Model):
productid = models.CharField(unique=True, max_length=20) # Field name made lowercase.
proddescription = models.CharField(db_column='prodDescription', max_length=255, blank=True, null=True) # Field name made lowercase.
packaging = models.ForeignKey(Packaging, on_delete=models.CASCADE, blank=True, null=True)
unitweight = models.FloatField(db_column='unitWeight', blank=True, null=True)
def __str__(self):
return self.productid

This would have been easier if you had the related model. But the commented-out slugrelatedfield is the way you should do it, using the actual field name:
prodName = serializers.SlugRelatedField(read_only=False, slug_field='productid')

Your serializer is wrong, You must use relationship serializer.
prodName = ProductlistSerializer(many = False)
But I found Your model defintion is very confusing

Related

AttributeError when attempting to get a value for field `country` on serializer

I'm running into the following error, and been stuck on it the last two weeks. I don't know what it could possibly mean by 'int' object has no attribute 'country' in my case, and country exists in my serializer and model. If I remove country from the serializer, I get the same error with post_code.
I haven't got a clue what could be going wrong
Got AttributeError when attempting to get a value for field `country` on serializer `AddressSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `int` instance.
Original exception text was: 'int' object has no attribute 'country'
View:
class Address(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [AddressPermission]
queryset = Addresses.objects.all()
def get_object(self):
try:
if self.request.COOKIES['access_token'] is not None:
obj = get_object_or_404(self.get_queryset(), user=NewUser.objects.get(id=jwt.decode(self.request.COOKIES['access_token'], settings.SECRET_KEY, algorithms=["HS256"])['user_id']))
self.check_object_permissions(self.request, obj)
return obj
except:
return status.HTTP_401_UNAUTHORIZED
serializer_class = AddressSerializer
Serializer:
class AddressSerializer(serializers.ModelSerializer):
class Meta:
fields = ('country', 'organization_name', 'administrative_area', 'sub_administrative_area', 'locality', 'post_code', 'thoroughfare', 'premise')
model = Addresses
Model:
class Addresses(models.Model):
country = models.CharField(max_length=2)
organization_name = models.CharField(max_length=150, null=True, blank=True)
# State/Province
administrative_area = models.CharField(max_length=150, null=True, blank=True)
# County/District/Municipality
sub_administrative_area = models.CharField(max_length=150, null=True, blank=True)
locality = models.CharField(max_length=150, null=True, blank=True)
post_code = models.CharField(max_length=12)
# the actual street address
thoroughfare = models.CharField(max_length=95)
# Apt, suite, or box number
premise = models.CharField(max_length=16)
user = models.ForeignKey(NewUser, on_delete=models.CASCADE)
Quickly looking at your code again i think you are getting the error because in your get_object you are returning an int and not a string.
In your exception, try to return an HTTPResponse as:
return HttpResponse(status=status.HTTP_401_UNAUTHORIZED)
I think the reason why when you remove country from the serializer you get the same error on the post_code is because all you other attributes have null=True, blank=True. Country does not have this and the next in line without is post_code. Although both of these should be empty strings anyway when saved.
So try to return an HttpResponse and let's see what happens.

django.core.exceptions.FieldError: Cannot resolve keyword 'date' into field. Join on 'date' not permitted

# Here is my models
This is my CustmerBuySell model DB designed.
class CustomerBuySell(models.Model):
customer = models.ForeignKey(CustomerAdd, on_delete=models.CASCADE)
customer_buy_sell_debit = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
customer_buy_sell_credit = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
description = models.CharField(max_length=250, blank=True)
date = models.DateField()
sms = models.BooleanField(default=False)
picture = models.ImageField(upload_to='customer_buy_sell_pics', default='images.png')
created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True)
updated_at = models.DateTimeField(auto_now=True, blank=True, null=True)
def __str__(self):
return self.customer.customer_name
class Meta:
verbose_name = "Customer BuySell"
verbose_name_plural = "Customer BuySell"
# Here, is my View.
This is the class-based APIView, which I have used. And try to use the aggregate query in this view.
class DailyCustomerBuySellAPIView(APIView):
def get(self, request):
customer_buy_sell = CustomerBuySell.objects.extra(select={'day': 'date( date )'}).values('day').order_by(
'date__date').annotate(available=Count('date__date'))
serializer = CustomerBuySellSerializer(customer_buy_sell, many=True)
return Response({"customer_buy_sell": serializer.data})
# And, finally here are my Serializers
I have no idea what's the problem! Please help me.
class CustomerBuySellSerializer(serializers.ModelSerializer):
# customer = CustomerAddSerializer()
class Meta:
model = CustomerBuySell
fields = '__all__'
def to_representation(self, instance):
representation = super(CustomerBuySellSerializer, self).to_representation(instance)
if instance.customer is not None:
customer_name = instance.customer.customer_name
previous_due = instance.customer.previous_due
representation['custo`enter code here`mer_name'] = customer_name
representation['previous_due'] = previous_due
return representation
There are many problems with your approach. Let me mention each of them one by one:
First of all remove date__date from your APIVIew
Before:
customer_buy_sell = CustomerBuySell.objects.extra(select={'day': 'date( date )'}).values('day').order_by(
'date__date').annotate(available=Count('date__date'))
Instead, write it as:
from django.db.models.functions import Extract
customer_buy_sell = CustomerBuySell.objects.annotate(day=Extract('date','day')).values('day').order_by('day')
if you need a count of the days then you can try
customer_buy_sell_count = customer_buy_sell.count()
Another thing that you are doing wrong is you pass a dict to serializer as you are already using values that return a dictionary of days and not object of CustomerBuySell so you do not need to pass it to serializer otherwise you have to do it according to your need.
In CustomerBuySellSerializer you are using a model serializer with __all__ while you are passing an extra fields day that is not part of it.
So in short there are so many syntax issues with your Django and Django Rest Framework.Great way to fix such issues is to set with an experience programmer so that he can improve the flow of the code. Later on you can focus on logic.
I suppose it is just a typo: Change date__date to date

Django submitting two forms cannot assign instance error

I have the following models/forms/view in which I have managed to submit to two different models as follows:
Models
class Account(models.Model):
username = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
name = models.CharField(max_length=150)
actflag = models.CharField(max_length=1, blank=True)
acttime = models.DateTimeField(blank=True, null=True)
comments = models.TextField(_('comments'), max_length=500, blank=True)
def __str__(self):
return self.name
class ISIN(models.Model):
code = models.CharField(max_length=12)
account_name = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
actflag = models.CharField(max_length=1, blank=True)
acttime = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.code
Forms
from apps.portfolio.models import Account, ISIN
class PortfolioForm(forms.ModelForm):
class Meta:
model = Account
fields = ['name', 'comments']
class IdentifierForm(forms.ModelForm):
class Meta:
model = ISIN
fields = ['code']
View
def portfolios(request):
if request.user.is_authenticated:
if request.POST:
fm = PortfolioForm(request.POST)
fm2 = IdentifierForm(request.POST)
if fm.is_valid():
messages.success(request, 'Portfolio has been created.')
account = fm.save(commit=False)
account.username = request.user
account.acttime = timezone.now()
account.actflag = 'I'
account.save()
isin = fm2.save(commit=False)
#isin.account_name = account.name
isin.acttime = timezone.now()
isin.actflag = 'I'
isin.save()
return redirect('portfolios')
else:
fm = PortfolioForm()
fm2 = IdentifierForm()
context = {"name": request.user, "form": fm, "form2": fm2}
return render(request, 'portfolios.html', context)
else:
return redirect('login')
However, you will notice the commented line in my view: isin.account_name = account.name, when I uncomment this line and try to submit the forms again I get the following error: Cannot assign "'test'": "ISIN.account_name" must be a "Account" instance.
I believe it's to do with ForeignKey but still unsure how to store the newly created account name the user submitted within the isin model.
Help is much appreciated.
Although my answer solves the problem you originally had, there are a couple additional points that I wanted to make.
Improve naming and fix the original error
Your field is called account_name, and it implies that a string will be stored there. If it was actually a string, you would be able to do what you tried:
isin.account_name = account.name
In reality, you have a ForeignKey to the Account model, so you have to actually save a reference to the account object:
isin.account_name = account
It's a really good idea to have a foreign key instead of just a string because it avoids denormalization.
The problem here is the name of the field, account_name. If you later want to access the account name, you would have to write something like isis.account_name.name. Sounds wrong, doesn't it?
You could solve this by renaming your field like so:
class ISIN(models.Model):
code = models.CharField(max_length=12)
account = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
actflag = models.CharField(max_length=1, blank=True)
acttime = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.code
Then, in your view, you would just isin.account = account, and later, if you wanted to access the name, you would use isin.account.name.
Another minor thing is that in some places an account is called Account and in other places it's Portfolio. This creates an illusion that they're unrelated entities and makes your code harder to read and maintain.
You probably should decide which one is the better term, and make it consistent everywhere.
Use builtin timestamp mechanism
Looks like you're using the acttime field to manually store creation time of accounts and ISINs.
You could use Django's auto_now_add property to do that automatically, like so:
class Account(models.Model):
acttime = models.DateTimeField(auto_now_add=True)
If you also wanted to store the last time an Account was updated, you could use auto_now (also renamed fields here for clarity):
class Account(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
And to stay DRY, you could make a mixin for that and use it in both Account and ISIN:
class TimeStampMixin(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Account(TimeStampMixin, models.Model):
username = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
name = models.CharField(max_length=150)
actflag = models.CharField(max_length=1, blank=True)
comments = models.TextField(_('comments'), max_length=500, blank=True)
def __str__(self):
return self.name
class ISIN(TimeStampMixin, models.Model):
code = models.CharField(max_length=12)
account = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
actflag = models.CharField(max_length=1, blank=True)
def __str__(self):
return self.code
This way, the creation time and the latest update time are automatically stored in your models (the ones that inherit from TimeStampMixin).
Validate both forms
Looks like you're only checking one of the forms for validity, and not the other:
if fm.is_valid():
You should probably check both, in case ISIN.code is invalid:
if fm.is_valid() and fm2.is_valid():
What it means is that you have to make an instance of the account model by getting the name in order to save the form like so:
def portfolios(request):
if request.user.is_authenticated:
if request.POST:
fm = PortfolioForm(request.POST)
fm2 = IdentifierForm(request.POST)
if fm.is_valid():
messages.success(request, 'Portfolio has been created.')
account = fm.save(commit=False)
account.username = request.user
account.acttime = timezone.now()
account.actflag = 'I'
account.save()
# Here is where we get the instance of account
account = Account.objects.get(name=account.name)
isin = fm2.save(commit=False)
isin.account_name = account
isin.acttime = timezone.now()
isin.actflag = 'I'
isin.save()
return redirect('portfolios')
else:
fm = PortfolioForm()
fm2 = IdentifierForm()
context = {"name": request.user, "form": fm, "form2": fm2}
return render(request, 'portfolios.html', context)
else:
return redirect('login')
The field account_name is a ForeignKey to Account, but you are assigning an string. You should to assign an Account.
Change:
isin.account_name = account.name
To:
isin.account_name = account

TypeError: 'int' object is not iterable serializer Django Serializer for one-to-many

I am facing a strange situation, Following is my code for models.
class Sector(models.Model):
sector_name = models.CharField(max_length=255, null=False)
sector_desc = models.CharField(max_length=1024, null=False)
def __set__(self):
return "{} - {}".format(self.sector_name, self.sector_desc)
class TrailCompany(models.Model):
sector = models.ForeignKey(Sector, on_delete=models.CASCADE, related_name="sector_id")
comp_name = models.CharField(max_length=255, null=False)
comp_desc = models.CharField(max_length=1024, null=False)
def __set__(self):
return "{} - {}".format(self.sector, self.comp_name, self.comp_desc)
class Trail(models.Model):
company = models.ForeignKey(TrailCompany, on_delete=models.CASCADE, related_name="company_id")
trail_id = models.CharField(max_length=255, null=False)
tri = models.CharField(max_length=255, null=False)
exp_pdufa = models.CharField(max_length=255, null=False)
def __set__(self):
return "{} - {}".format(self.company, self.exp_pdufa, self.trail_id, self.tri, self.exp_pdufa)
Following is my code for the serializer,
class SectorSerializer(serializers.ModelSerializer):
class Meta:
model = Sector
fields = '__all__'
class TrailCompanySerializer(serializers.ModelSerializer):
sectors = SectorSerializer(source="sector_id", many=True)
class Meta:
model = TrailCompany
fields = '__all__'
class TrailSerializer(serializers.ModelSerializer):
companies = TrailCompanySerializer(source="company_id", many=True)
class Meta:
model = Trail
fields = '__all__'
When I try to serilize object I am getting the stated error,
trail_query = Trail.objects.all()
trails = TrailSerializer(trail_query, many=True)
return Response({"success": True, 'trails': trails.data})
Please help me to figure out and resolve the problem. Thanks.
Looks like your error is in the following line:
companies = TrailCompanySerializer(source="company_id", many=True)
Trail<>TrailCompany is a one to one relationship from the perspective of Trail. E.g. the Trail only knows of one TrailCompany it is related to. Therefore you do not need the many=True attribute.
I also think the source should be company rather than company_id. company_id is a primary key, whereas company is the related object
Selected answer doesn't work for me. However following way worked:
company = TrailCompanySerializer(many=True)
Also remember to put company in related_name field like this:
company = models.ForeignKey(TrailCompany, on_delete=models.CASCADE, related_name="company")

How to set automatically 'many' flag of django serializer depending on input being a list or a single item

I have the following machine model.
class Machine(models.Model):
operators = models.ManyToManyField(User, related_name='machines', blank=True)
elasticsearch_id = models.PositiveIntegerField(default=None, null=True, blank=True)
company = models.ForeignKey(Company, default=None, null=True, blank=True,on_delete=models.SET_DEFAULT)
machine_brand = models.CharField(max_length=200, null=False)
machine_model = models.CharField(max_length=200, default='')
machine_picture = models.URLField(max_length=200, null=True)
tools = models.ManyToManyField('Tool', default=None, blank=True)
clustered_tags = JSONField(null=True)
elasticsearch_tags = JSONField(null=True, blank=True, default=DEFAULT_TAG_MAP)
machine_slug = models.SlugField()
With the following serializer.
class MachineSerializer(serializers.ModelSerializer):
class Meta:
model = Machine
fields = '__all__'
In my views, I am filtering the data on the company the logged in users belongs to. Now, I want to serialize the object and return it to the client. However, I don't know beforehand whether the queryset is a list of objects or a single object so that I can set the many flag of the serializer to true or false.
#api_view(['GET','POST'])
def manage_operators(request):
user_machines = Machine.objects.filter(company=request.user.company)
user_machines_ser = MachineSerializer(user_machines, many=True)
return Response({'machines': user_machines_ser.data})
Is there any elegant way to solve this? I could solve it this way but there must be a better way of doing it.
if len(user_machines) > 0 :
user_machine_ser = MachineSerializer(user_machines, many=True)
else:
user_machine_ser = MachineSerializer(user_machines, many=False)
Any input much appreciated!
Since you are fetching a QuerySet every time, you don't have to set many=False if there is only one item in the QuerySet.
So you can safely use
user_machine_ser = MachineSerializer(user_machines, many=True)
everytime, no matter how many objects are in the QuerySet.
Since you are passing a QuerySet, you can use the count() [Django doc] method in the __init__() method of MachineSerializer by overriding it.
class MachineSerializer(serializers.ModelSerializer):
class Meta:
model = Machine
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs['instance'].count() > 1: # count() method used here <<<<<
kwargs['many'] = True
else:
kwargs['many'] = False