I have two models Purchaser and paymentInvoice, I want to make sure i don't create a duplicate Purchaser object when i'm creating a new paymentInvoice for the same Purchaser individual/instance.
Basically i have a Purchaser by the name Becky, so when i want to create an invoice for Becky 1st i want to make sure if the name Becky exists in Purchaser if it does, create paymentInvoice object with Becky taking the field invoiceOwner. If Becky doesn't exist in Purchaser, create an instance of that purchaser in Purchaser then use that instance name to create paymentInvoice object.
Models file
class Purchaser(models.Model):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=20)
email = models.EmailField(max_length=255, blank=True, null=True)
image = models.ImageField(default='default.png', upload_to='customer_photos/%Y/%m/%d/')
data_added = models.DateField(default=datetime.date.today)
def __str__(self):
return self.name
class paymentInvoice(models.Model):
invoiceNo = models.CharField(max_length=50, unique=True, default=increment_invoice_number)
invoiceOwner = models.ForeignKey(Purchaser, on_delete=models.CASCADE, related_name="invoice_detail")
product = models.CharField(max_length=50, blank=True)
date = models.DateField(default=datetime.date.today)
quantity = models.PositiveSmallIntegerField(blank=True, default=1)
payment_made = models.IntegerField(default=0)
def __str__(self):
return self.invoiceOwner.name
Serializers file
class paymentInvoiceSerializer(serializers.ModelSerializer):
invoiceOwner = purchaserSerializer(many=False)
invoiceOwner = serializers.CharField(source='invoiceOwner.name')
class Meta:
model = paymentInvoice
fields = '__all__'
def create(self, validated_data):
purchaser_data = validated_data.pop("invoiceOwner")
purchaser, _ = Purchaser.objects.get_or_create(**purchaser_data).first()
validated_data.update({"invoiceOwner": purchaser})
return paymentInvoice.objects.create(**validated_data)
Views file
class PurchaserListCreateView(ListCreateAPIView):
serializer_class = purchaserSerializer
queryset = Purchaser.objects.all()
class paymentInvoiceListCreateView(ListCreateAPIView):
serializer_class = paymentInvoiceSerializer
queryset = paymentInvoice.objects.all().order_by('-date')
POST request in Postman for the model paymentInvoice
{
"invoiceOwner":"Becky",
"product": "Lix",
"quantity": 1,
"payment_made": 3000
}
My way is not working, i get the error from PostMan
MultipleObjectsReturned at /api/clients/invoice/
get() returned more than one Purchaser -- it returned 2!
You can first check for the record if not found then create one like below
def create(self, validated_data):
purchaser_data = validated_data.pop("invoiceOwner")
purchaser = Purchaser.objects.filter(**purchaser_data).first()
if purchaser is None:
purchaser = Purchaser.objects.create(**purchaser_data)
validated_data.update({"invoiceOwner": purchaser})
return paymentInvoice.objects.create(**validated_data)
Probably get_or_create should work also, just i think you should redifine get_or_create not create method .
def get_or_create(self):
purchaser_data = validated_data.pop("invoiceOwner")
purchase,created = Purchase.objects.get_or_create(**purchaser_data)
validated_data.update({"invoiceOwner": purchaser})
payment_invoice = paymentInvoice.objects.create(**validated_data)
return payment_invoice,created
in view :
model_serializer = paymentInvoiceSerializer(data=request.data)
if model_serializer.is_valid():
payment_invoice,_ = model_serializer.get_or_create()
Related
I have two models in different apps like so:
class Account(models.Model):
"""
Class to store fiat account information of a companies bank account
"""
number = models.CharField(max_length=100)
currency = models.ForeignKey(FiatCurrency, on_delete=models.CASCADE)
owner = models.ForeignKey(Company, on_delete=models.CASCADE)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.number
class FiatTransaction(models.Model):
"""
Class to store Transactions made between escrow and operative white-listed fiat accounts
"""
debit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='debit_account')
credit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='credit_account')
executed_on = models.DateTimeField(auto_now_add=True)
amount = models.FloatField()
currency = debit_account.currency
is_processed = models.BooleanField(default=False)
fee = models.FloatField()
memo = models.CharField(max_length=250)
def __str__(self):
return F"Transferred {self.amount} from {self.debit_account} to {self.credit_account} at {self.executed_on}"
Now the field currency of model FiatTransaction doesn't seem to work the way I intend it to do. It raises
AttributeError: 'ForeignKey' object has no attribute 'currency'
# Source model
class FiatCurrency(models.Model):
"""
A model to store Fiat Currencies offered by Finchin to
include into cash-pools.
"""
ISO_Code = models.CharField(max_length=3)
name = models.CharField(max_length=50)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
Why's that and how to make this work?
You can make a #property that will determine the currency of that object with:
class FiatTransaction(models.Model):
debit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='debit_account')
credit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='credit_account')
executed_on = models.DateTimeField(auto_now_add=True)
amount = models.FloatField()
is_processed = models.BooleanField(default=False)
fee = models.FloatField()
memo = models.CharField(max_length=250)
#property
def currency(self):
return self.debit_account.currency
This can however be inefficient if you have to do this for a lot of FiatTransactions.
In that case it might be better to remove the currency property, and annotate the QuerySet with:
from django.db.models import F
FiatTransaction.objects.annotate(currency=F('debit_account__currency'))
The FiatTransactions that arise from this will have an extra attribute named .currency that will contain the .currency of the .debit_account.
If you need this often, you can make use of a Manager that will automatically annotate when you access FiatTransaction.objects:
from django.db.models import F
class FiatTransactionManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).annotate(
currency=F('debit_account__currency')
)
class FiatTransaction(models.Model):
# …
objects = FiatTransactionManager()
In my django app, i'm having difficulties whenever i want to add a new object that uses the table paymentInvoice.
The error i'm getting from my api looks like this
IntegrityError at /api/clients/invoice/
null value in column "invoiceOwner_id" violates not-null constraint
DETAIL: Failing row contains (10, INV-0006, Lix, 2020-08-04, 1, Pending, 3000, null).
NB: I haven't created the field invoiceOwner_id, postgres automatically added it or rather is using it as a representation for my invoiceOwner field
class Purchaser(models.Model):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=20, unique=True)
email = models.EmailField(max_length=255, unique=True, blank=True)
image = models.ImageField(default='default.png', upload_to='customer_photos/%Y/%m/%d/')
data_added = models.DateField(default=datetime.date.today)
def __str__(self):
return self.name
class paymentInvoice(models.Model):
invoiceNo = models.CharField(max_length=50, unique=True, default=increment_invoice_number)
invoiceOwner = models.ForeignKey(Purchaser, on_delete=models.CASCADE, related_name="invoice_detail")
product = models.CharField(max_length=50, blank=True)
date = models.DateField(default=datetime.date.today)
quantity = models.PositiveSmallIntegerField(blank=True, default=1)
payment_made = models.IntegerField(default=0)
def __str__(self):
return self.invoiceOwner.name
serilizers file
class paymentInvoiceSerializer(serializers.ModelSerializer):
invoiceOwner = serializers.SerializerMethodField()
class Meta:
model = paymentInvoice
fields = '__all__'
def get_invoiceOwner(self, instance):
return instance.invoiceOwner.name
views file
class paymentInvoiceListCreateView(ListCreateAPIView):
serializer_class = paymentInvoiceSerializer
queryset = paymentInvoice.objects.all().order_by('-date')
GET result from api call.
{
"id": 1,
"invoiceOwner": "Martin",
"invoiceNo": "INV-0001",
"product": "",
"date": "2020-08-04",
"quantity": 1,
"payment_made": 0
}
Tried passing below as POST but got the main error
{
"invoiceOwner": "Becky",
"product": "Lix",
"quantity": 1,
"payment_made": 3000
}
"invoiceOwner" in your serializers.py is a SerializerMethodField which is readonly
that's why you get an error, you have to define the create method yourself
As I said in comment: You need to explicitly override the create method in your serializer since your model has foreign key invoiceOwner, just to create that instance first as a Purchaser instance.
You can try the code below:
class paymentInvoiceSerializer(serializers.ModelSerializer):
invoiceOwner = serializers.SerializerMethodField()
class Meta:
model = paymentInvoice
fields = '__all__'
def get_invoiceOwner(self, instance):
return instance.invoiceOwner.name
def create(self, validated_data):
purchaser_name = validated_data.get("invoiceOwner")
purchaser = Purchaser(name=purchaser_name,
# you need to have phone, email, since these fields are unique,
# they can't remain null
)
purchaser.save()
return paymentInvoice.objects.create(invoiceOwner = purchaser, **validated_data)
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
I am trying to create a product filter.
I am sending the user choice in URL
if the user select size = L then using request.GET
I am receiving:
{'size': ['L']}
But I want to receive: {'size':{'op':'in','attri':'L'}}
Is this possible?
Please help
my models are
class ProductAttribute(models.Model):
slug = models.SlugField(max_length=50, unique=True)
name = models.CharField(max_length=100)
op = models.CharField(max_length=20,default='in')
class Meta:
ordering = ('slug', )
def __str__(self):
return self.name
def get_formfield_name(self):
return slugify('attribute-%s' % self.slug, allow_unicode=True)
def has_values(self):
return self.values.exists()
class AttributeChoiceValue(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)
attribute = models.ForeignKey(
ProductAttribute, related_name='values', on_delete=models.CASCADE)
class Meta:
unique_together = ('name', 'attribute')
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=128)
attributes = HStoreField(default={})
q2 = AttributeChoiceValue.objects.filter(attribute__name='size')
My size filter(filter.py) is:
size = django_filters.ModelMultipleChoiceFilter(queryset=q2.values_list('name', flat=True).distinct(),widget=forms.CheckboxSelectMultiple)
I am currently using the following query to filter my database in views.py
result = Product.objects.all()
for key, value in request.GET:result = result.filter(**{'attributes__{}__in'.format(key): value})
I want to make it
a=request.GET
for key, value in a:
result = result.filter(**{'attributes__{}__{}'.format(key,a['op']): value})
so that if I even use Price range as filter my query filter accordingly will be
attributes__price__range
You can send info to your views via "path converters":
https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters
Or using regular expressions:
https://docs.djangoproject.com/en/2.0/topics/http/urls/#using-regular-expressions
I'm creating a Django (1.8) webapp that saves racing laptimes and scoreboards. The database is populated using an API built using Django Rest Framework. It's the first time I'm trying to build a proper api using rest framework.
A quick overview of the models:
Event, A racing event/weekend
Session, A single race/practice/quali - FK Event
Car, A car taking part in a session - FK Session
Lap, Laps for specific car - FK Car
The Event is created manually, but the rest is supposed to be "dynamic" (get or create)
Right now I'm trying to create a new car using my API, but I'm stuck. To get the cars event and session I'm trying to use the url;
/api/results/skrotbilsracet-29042016/r1/cars/
The idea is to post data to this url and "get or create" a new car object.
To get the correct session object for the new car session FK, I need to use a custom function that takes the kwargs and tries to find the session.
The more I read about how to solve this, the more confused I get.
Could someone push me in the right direction?
This is my latest attempt at solving this, which just gives me "{"session":["This field is required."]}"
models.py
class Session(models.Model):
session_types = (
('p', 'Practice'),
('q', 'Qualification'),
('r', 'Race')
)
event_id = models.ForeignKey(Event, related_name='sessions')
name = models.CharField(max_length=2, blank=True)
current_session = models.BooleanField(default=True)
session_type = models.CharField(max_length=2,
choices=session_types)
started = models.DateTimeField(auto_now_add=True)
ended = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['started']
def save(self):
if not self.name:
# Get number of sessions
session_count = Session.objects.filter(event_id=self.event_id)\
.filter(session_type=self.session_type)\
.count()
session_count += 1
self.name = self.session_type + str(session_count)
super(Session, self).save()
def __unicode__(self):
string = self.started.strftime("%d-%m-%Y %H:%M") + ' - '
string += self.name.upper()
return(string)
class Car(models.Model):
session = models.ForeignKey(Session, related_name='cars')
number = models.IntegerField()
full_name = models.CharField(max_length=256, blank=True)
short_name = models.CharField(max_length=256, blank=True)
race_class = models.CharField(max_length=50, blank=True)
best_lap = models.IntegerField(blank=True, null=True)
best_lap_time = models.CharField(max_length=20, blank=True)
best_sector1 = models.CharField(max_length=20, blank=True)
best_sector2 = models.CharField(max_length=20, blank=True)
best_sector3 = models.CharField(max_length=20, blank=True)
best_speed = models.IntegerField(blank=True, null=True)
pitstops = models.IntegerField(blank=True, null=True)
total_time = models.CharField(max_length=20, blank=True)
transponder = models.CharField(max_length=50, blank=True)
apiUrls.py
urlpatterns = [
url(r'^raceslug/$', raceSlugView.as_view(), name='race-slug'),
url(r'^events/$', eventsView.as_view(), name='event-list'),
url(r'^session/$', getSessionView.as_view(), name='session-pk'),
url(r'^(?P<event_id>[a-z0-9\-]+)/$', eventView.as_view(), name='event-detail'),
url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/$', sessionView.as_view(), name='session-detail'),
url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/cars/$', carsView.as_view(), name='car-list'),
url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/(?P<number>[0-9]+)/$', carView.as_view(), name='car-detail'),
]
urlpatterns = format_suffix_patterns(urlpatterns)
api.py
class carsView(generics.ListCreateAPIView):
serializer_class = carSerializer
def get_session(self, event_id, name):
print('Getting session')
# Get event object
try:
event = Event.objects.get(event_id=event_id)
print('Found event')
except ObjectDoesNotExist:
print('Did not find event')
return
# Get session object
try:
session = event.sessions.get(name=name)
print('Found session: ', session)
return session
except ObjectDoesNotExist:
print('Did not find session')
return
def get_queryset(self):
print('Getting queryset')
print('event_id: ' + self.kwargs['event_id'])
print('name: ' + self.kwargs['name'])
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
return(Car.objects.filter(session=session.pk))
def perform_create(self, serializer):
print('Creating new car')
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
serializer.save(session=session)
serializers.py
class carSerializer(serializers.ModelSerializer):
laps = lapSerializer(many=True, read_only=True)
class Meta:
model = Car
fields = (
'session',
'number',
'full_name',
'short_name',
'race_class',
'best_lap',
'best_lap_time',
'best_sector1',
'best_sector2',
'best_sector3',
'best_speed',
'pitstops',
'total_time',
'transponder',
'laps')
Solution:
This is what I actually changed to get it working.
api.py
from rest_framework.serializers import ValidationError
class carsView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
print('Creating new car')
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
number = self.request.POST.get('number')
car = session.cars.filter(number=number)
if car.exists():
raise ValidationError('Car already exists')
serializer.save(session=session)
serializers.py
class carSerializer(serializers.ModelSerializer):
laps = lapSerializer(many=True, read_only=True)
session = serializers.StringRelatedField(required=False)
...
I see that you're creating your session ID there:
def get_queryset(self):
...
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
return(Car.objects.filter(session=session.pk))
Then you don't need it in a serializer, only in a model. So you can set it a snot required in a serializer, but it will still be required in a model.
I guess this answer could help you: Django REST Framework serializer field required=false