How to do field validation in django-import-export - django

Following is my model:
class Product(models.Model):
product_title = models.CharField(max_length=100, null=False,
verbose_name='Product title')
product_description = models.TextField(max_length=250,
verbose_name='Product description')
product_qty = models.IntegerField(verbose_name='Quantity')
product_mrp = models.FloatField(verbose_name='Maximum retail price')
product_offer_price = models.FloatField(verbose_name='Selling price')
I wanted to have a validation for product_offer_price field before save for which I had posted a QUESTION and it was answered with the working solution.
Validation needed is:
if product_offer_price > product_mrp:
raise ValidationError
Now the solution to above question works perfectly for the admin forms.
But, I have implemented django-import-export, in which I am importing Product Data in bulk in admin, and I need similar validation during bulk import.
How to achieve this?

Well, here was a little research process.
And finally I got it.
The trouble is avoiding ProductForm in import-export library.
Inside library import invoke method save() of instance, but if we raise ValidationError in Model (not in Form) = 500 with DEBUG = False, and traceback page with DEBUG = True.
So we should use "before_import" method in import_export Resource and "clean" method in django.forms Form.
admin.py
from forms import ProductForm
from models import Product
from import_export import resources
from import_export.admin import ImportExportActionModelAdmin
from django.forms import ValidationError
class ProductResource(resources.ModelResource):
class Meta:
model = Product
def before_import(self, dataset, using_transactions, dry_run, **kwargs):
for row in dataset:
if int(row[4]) < int(row[5]):
raise ValidationError('Product offer price cannot be greater than Product MRP. '
'Error in row with id = %s' % row[0])
class ProductAdmin(ImportExportActionModelAdmin):
list_display = ('product_title', 'product_description', 'product_qty', 'product_mrp', 'product_offer_price')
form = ProductForm
resource_class = ProductResource
admin.site.register(Product, ProductAdmin)
forms.py
from django import forms
from models import Product
class ProductForm(forms.ModelForm):
class Meta:
model = Product
exclude = [id, ]
def clean(self):
product_offer_price = self.cleaned_data.get('product_offer_price')
product_mrp = self.cleaned_data.get('product_mrp')
if product_offer_price > product_mrp:
raise forms.ValidationError("Product offer price cannot be greater than Product MRP.")
return self.cleaned_data
models.py
class Product(models.Model):
product_title = models.CharField(max_length=100, null=False, verbose_name='Product title')
product_description = models.TextField(max_length=250, verbose_name='Product description')
product_qty = models.IntegerField(verbose_name='Quantity')
product_mrp = models.FloatField(verbose_name='Maximum retail price')
product_offer_price = models.FloatField(verbose_name='Selling price')

An easier way might be to hook into the import/export workflow by adding a custom before_import_row method to your resource class:
class ProductResource(resources.ModelResource):
class Meta:
model = Product
def before_import_row(self, row, **kwargs):
if int(row[4]) < int(row[5]):
raise ValidationError('Product offer price cannot be greater than Product MRP. '
'Error in row with id = %s' % row[0])

here is another Simple Way For Django Rest Framework
def importcsv(request, company):
for row in dataset['company']:
if(row != company):
raise PermissionDenied("You do not have permission to Enter Clients in Other Company, Be Careful")

Related

Django Rest API issue with retrieving correct information from urls. It is a Grandparent - Parent - Child relationship. How do I correctly map them?

I have been stuck on this problem for about 9 hours and am having trouble understanding the Grandparent - Parent - Child relationship with a One to Many Relationship(foreign key) in Django.
I am currently creating an E-Commerce website and am trying to display the information in 4 different ways to fetch from my React frontend.
I have 3 models setup:
Category
Subcategory (Foreign key to Category)
Products (Foreign Key to Subcategory)
This is the result I am attempting to create in my urls/views/serializer files:
For some reason I can successfully retrieve at a granular level for a single product but as soon as I start going up nested models I am running into the following errors:
Desired Result
I have api/ included in the core urls folder already.
I want JSON to display the information like this.
# URLSearch api/Category/
# Category -
# |
# Subcategory 1 -
# |
# Product 1
# Product 2
# Subcategory 2 -
# |
# Product 1
# Proudct 2
I also want JSON to display the information like this.
# URLSearch api/Category/Subcategory/
# Category -
# |
# Subcategory -
# |
# Product 1
# Product 2
Errors
Error 1:
Got AttributeError when attempting to get a value for field slug on serializer SubcategoryListSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the QuerySet instance.
Original exception text was: 'QuerySet' object has no attribute 'slug'.
Error 2:
Returns Wrong Object piggies back off issue above - I deleted my code to clean it up file as I have been attempting this for 10+ hours so there is currently a place holder route there.
Urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from api import views
urlpatterns = [
path('search/<slug:category_slug>/<slug:subcategory_slug>/',
views.SubcategoryListView.as_view()), #Error 1
path('search/<slug:category_slug>/', views.AllProductsListView.as_view()), # Error 2
path('all-products/', views.AllProductsListView.as_view()), # Good
path('search/<slug:category_slug>/<slug:subcategory_slug>/<slug:product_slug>',
views.ProductDetail.as_view()), # WORKS
# path('all-products/', views.AllProductsListView.as_view())
]
Models.py
from django.db import models
import uuid
# Create your models here.
class Category(models.Model):
slug = models.SlugField(
primary_key=True, max_length=50, null=False, unique=True)
class Meta:
ordering = ('slug',)
def __str__(self):
return self.slug
def get_absolute_url(self):
return f'/{self.slug}/'
class Subcategory(models.Model):
slug = models.SlugField(
primary_key=True, max_length=50, null=False, unique=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
class Meta:
ordering = ('category',)
def __str__(self):
return self.slug
def get_absolute_url(self):
return f'/{self.category.slug}/{self.slug}/'
class Products(models.Model):
subcategory = models.ForeignKey(Subcategory, on_delete=models.CASCADE)
newAdd = models.BooleanField(default=False)
name = models.CharField(max_length=200)
slug = models.SlugField(
primary_key=True, max_length=50, null=False, unique=True)
imageOne = models.CharField(max_length=100)
imageTwo = models.CharField(max_length=100, null=True)
imageThree = models.CharField(max_length=100, null=True)
colors = models.CharField(max_length=300) # This is going to be a list
price = models.FloatField()
dateCreated = models.DateField(auto_now_add=True)
class Meta:
ordering = ('-dateCreated',)
def __str__(self):
return self.slug
def get_absolute_url(self):
return f'/{self.subcategory.category.slug}/{self.subcategory.slug}/{self.slug}'
Views
from django.shortcuts import render
from .serializers import AllProductsSerializer, CategorySerializer, SubcategoryListSerializer, ProductsSerializer, SubcategorySerializer
from rest_framework import viewsets, generics, views, response
from products.models import Products, Subcategory, Category
# Create your views here.
class ProductDetail(views.APIView):
def get_object(self, category_slug, subcategory_slug, product_slug):
obj = Products.objects.filter(subcategory__category__slug=category_slug).filter(
subcategory=subcategory_slug).get(slug=product_slug)
return obj
def get(self, request, category_slug, subcategory_slug, product_slug):
print(request)
product = self.get_object(
category_slug, subcategory_slug, product_slug)
serializer = ProductsSerializer(product)
return response.Response(serializer.data)
class SubcategoryListView(views.APIView):
def get_object(self, category_slug, subcategory_slug):
obj = Products.objects.filter(
subcategory__category__slug=category_slug)
return obj
def get(self, request, category_slug, subcategory_slug):
products = self.get_object(category_slug, subcategory_slug)
serializer = SubcategoryListSerializer(products)
return response.Response(serializer.data)
class AllProductsListView(generics.ListAPIView):
serializer_class = AllProductsSerializer
queryset = Products.objects.all()
class CategoryListView(generics.ListCreateAPIView):
queryset = Products.objects.all()
serializer_class = Category
Serializers
from rest_framework import serializers
from products.models import Products, Category, Subcategory
class ProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = (
'newAdd',
'name',
'slug',
'imageOne',
'price',
'dateCreated',
'get_absolute_url',
)
class SubcategoryListSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = ('__all__')
class SubcategorySerializer(serializers.ModelSerializer):
items = SubcategoryListSerializer(many=True, read_only=True)
class Meta:
model = Subcategory
fields = (
'slug',
'items'
'get_absolute_url',
)
class AllProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('__all__')
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = ('__all__')

Nested serializers and data representation

Working on a django project I am a bit stuck on data representation through APIs. In fact when designing models the data model is quite stratighforward : I have a one to many relationship A--> B
therefore I have added a FK to object B.
Object B has a boolean attribute "active".
I would like to make an API call to list all A objects having at least one assoicated object B with active = true.
The api could be like this :
/api/objectA/?ObjectB.active=True
Here is my code :
Models :
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
class Startup(models.Model):
header = models.CharField("Header", max_length=255)
title = models.CharField("Title", max_length=255)
description = models.CharField("description", max_length=255)
# TODO Change this to options instead of array
tags = ArrayField(models.CharField(max_length=10, blank=True), size=5)
# TODO Images to be stored in aws only url will be in DB
card_image = models.ImageField(upload_to='media/images/cards')
logo_image = models.ImageField(upload_to='media/images/logos')
main_img = models.ImageField(upload_to='media/images/main', null=True)
createdAt = models.DateTimeField("Created At", auto_now_add=True)
def __str__(self):
return self.title
class Investment(models.Model):
# TODO change the name of Investment to fund round in back and front
# TODO all price to be checked for max digits and decimal places
startup = models.ForeignKey(Startup, related_name='startup_investments', on_delete=models.CASCADE, default="1")
# Use the related_name as a serializer bale for investments inside startup serializer
Investment_title = models.CharField("Investment_title", max_length=255, default="Missing Title")
collected_amount = models.DecimalField(max_digits=12, decimal_places=2)
goal_percentage = models.IntegerField(default=0)
number_of_investors = models.IntegerField(default=0)
days_left = models.IntegerField()
active = models.BooleanField(default=False)
# TODO Need to update this to prevent linking to a non existing startup
createdAt = models.DateTimeField("Created At", auto_now_add=True)
def clean(self):
"""Validate that the startup does not have already an active Investment """
if self.active:
qs = Investment.objects.filter(active=True).filter(startup=self.startup)
if self.pk is not None:
qs = qs.exclude(pk=self.pk)
if qs:
raise ValidationError(message="An active investment already exists for this startup")
def __str__(self):
return self.Investment_title
Serializers :
from rest_framework import serializers
from .models import Startup, Investment
class InvestmentSerializer(serializers.ModelSerializer):
class Meta:
model = Investment
fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
'days_left', 'active')
class StartupSerializer(serializers.ModelSerializer):
startup_investments = InvestmentSerializer(many=True, read_only=True)
class Meta:
model = Startup
fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
'logo_image', 'main_img', 'startup_investments')
Views :
from django_filters import rest_framework as filters
from rest_framework.viewsets import ModelViewSet
from rest_framework_extensions.mixins import NestedViewSetMixin
from .serializers import *
class StartUpViewSet(NestedViewSetMixin, ModelViewSet):
"""
Class that provides List, Retrieve, Create, Update, Partial Update and Destroy actions for startups.
It also include a filter by startup status
"""
model = Startup
queryset = Startup.objects.all()
serializer_class = StartupSerializer
class InvestmentViewSet(NestedViewSetMixin, ModelViewSet):
"""
Class that provides List, Retrieve, Create, Update, Partial Update and Destroy actions for Investments.
It also include a active and investment title
"""
model = Investment
serializer_class = InvestmentSerializer
queryset = Investment.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('active', 'Investment_title')
routers :
router = ExtendedSimpleRouter()
(
router.register(r'api/investments', views.InvestmentViewSet, basename='investment'),
router.register(r'api/startups', views.StartUpViewSet, basename='startup')
.register(r'investments', views.InvestmentViewSet, basename='startups_investment',
parents_query_lookups=['startup']),
)
Thanks for your help.
I would try something like this:
class StartUpViewSet(NestedViewSetMixin, ModelViewSet):
model = Startup
#queryset = Startup.objects.all()
serializer_class = StartupSerializer
def get_queryset(self):
Startup.objects.annotate(active_investments=Count('startup_investments', filter=Q(startup_investments__active=True)).filter(active_investments__gt=0)
Hello I am posting this answer hoping it will help others as I have spent two days to make this work!!
class ActiveStartupSerializer(serializers.ListSerializer):
def to_representation(self, data):
"""List all startups with one active investment"""
data = data.filter(startup_investments__active=True)
return super(ActiveStartupSerializer, self).to_representation(data)
class Meta:
model = Startup
fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
'logo_image', 'main_img', 'startup_investments')
class InvestmentSerializer(serializers.ModelSerializer):
class Meta:
model = Investment
fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
'days_left', 'active')
class StartupSerializer(serializers.ModelSerializer):
startup_investments = InvestmentSerializer(many=True, read_only=True)
class Meta:
model = Startup
list_serializer_class = ActiveStartupSerializer
fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
'logo_image', 'main_img', 'startup_investments')

Django foreign key issue with django-import-export library (IntegrityError at /import/ FOREIGN KEY constraint failed)

I'm relatively new to Django and not an advanced programmer, so please pardon my ignorance.
What is working:
I have a Django application that uses one main model which connects to two secondary models with foreign keys. The application can correctly create companies from template and from admin, and can correctly display the "niche" drop-down field using a foreign key to the Category model and can correctly display the images using a foreign key from the CompanyImage model.
What is not working:
The django-import-export library can correctly import an XLS document from front end and from admin, but ONLY if I disable the Category and CompanyImage model that are relying on foreign keys. The library does import correctly with the default user=models.ForeignKey(User) in my main Company model, but the foreign keys that connect to the secondary models are causing a foreign key error: IntegrityError at /import/ FOREIGN KEY constraint failed.
What I need
The XLS sheet I am importing does not import the fields that use a foreign key, so I would like to disable those fields to avoid the foreign key error. It would be nice to import a niche/category field, but I can do without.
What I've tried
I've spent two days trying to fix this problem.
I've tried reading the django-import-export documentation.
I've tried adding list_filter and exclude in class Meta for the Resource model.
I've read through Dealing with import of foreignKeys in django-import-export.
I've read through foreign key in django-import-export.
I would be very grateful someone can help steer me in the right direction. Thank you.
Models.py
from django.db import models
from django.contrib.auth.models import User
from phonenumber_field.modelfields import PhoneNumberField
#had to use pip install django-phone-verify==0.1.1
from django.utils import timezone
import uuid
from django.template.defaultfilters import slugify
class Category(models.Model):
kind = models.CharField(verbose_name='Business Type',max_length=100,blank=True,)
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return self.kind
class Company(models.Model):
#BASIC
title = models.CharField(verbose_name='company name',max_length=100,blank=True)
contact = models.CharField(verbose_name='director',max_length=100,blank=True)
phone_number = PhoneNumberField(blank=True)
email = models.EmailField(max_length=200,blank=True)
email_host = models.CharField(max_length=200,blank=True)
website = models.URLField(max_length=200,blank=True)
facebook = models.URLField(max_length=200,blank=True)
memo = models.TextField(blank=True)
niche = models.ForeignKey(Category, default=0000,on_delete=models.SET_DEFAULT)
#UPLOADS
profile_picture = models.ImageField(upload_to='prospects/images/', blank=True)
image = models.ImageField(upload_to='prospects/images/', blank=True)
file = models.FileField(upload_to='prospects/uploads', blank=True)
#TIME
date = models.DateField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
datecompleted = models.DateTimeField(null=True, blank=True) #null for datetime object
#BOOLIANS
important = models.BooleanField(default=False)
cold = models.BooleanField(default=False, verbose_name='these are cold leads')
warm = models.BooleanField(default=False, verbose_name='these are warm leads')
hot = models.BooleanField(default=False, verbose_name='these are hot leads')
#USER
user = models.ForeignKey(User, on_delete=models.CASCADE,null=True,blank=True)
#TEST MODEL
decimal = models.DecimalField(max_digits=5, decimal_places=2, blank=True, default=00.00)
integer = models.IntegerField(blank=True, default=0000)
positive_int = models.PositiveIntegerField(null=True, blank=True, default=0000)
positive_small_int = models.PositiveSmallIntegerField(null=True, blank=True, default=0000)
#ADMIN CONSOLE
class Meta:
verbose_name_plural = "Companies"
def __str__(self):
if self.title == "":
print('empty string')
return "No Name"
elif type(self.title) == str:
return self.title
else:
return "No Name"
# this makes the title appear in admin console instead of object number
class CompanyImage(models.Model):
company = models.ForeignKey(Company, default=None, on_delete=models.CASCADE)
image = models.FileField(upload_to = 'prospects/images/',blank=True)
def __str__(self):
return self.company.title
resource.py
from import_export import resources
# from import_export import fields
from import_export.fields import Field
from import_export.fields import widgets
from .models import Company
from django.utils.encoding import force_str, smart_str
# The following widget is to fix an issue with import-export module where if i import any number from an xls file, it imports as a float with a trailing ,0
#could keep it a number and use trunc function to take away decimal but will make string
class DecimalWidget(widgets.NumberWidget):
def clean(self, value, row=None, *args, **kwargs):
print()
print(f"type of value is {type(value)}")
print()
if self.is_empty(value):
return ""
elif type(value) == float:
new_string = force_str(value)
seperator = '.'
new_string_witout_0 = new_string.split(seperator, 1)[0]
print()
print(f"the new type of value is {type(value)}")
print(f"the new value is {value}")
print()
return new_string_witout_0
else:
print("Aborting! it's not a float or empty string. will just return it as it is.")
return value
print()
print(f"type of value is {type(value)}")
print(f" the value returned is {value}")
print()
class CompanyResource(resources.ModelResource):
title = Field(attribute='title', column_name='name',widget=DecimalWidget())
contact = Field(attribute='contact', column_name='contact',widget=DecimalWidget())
phone_number = Field(attribute='phone_number', column_name='phone',widget=DecimalWidget())
# niche = Field(attribute='niche', column_name='niche',widget=DecimalWidget())
class Meta:
model = Company
exclude = ('niche')
fields = ('id','title','contact','phone_number', 'email','email_host','website','facebook')
export_order = ['id','title','contact','phone_number', 'email','email_host','website','facebook']
# fields = ( 'id', 'weight' )
admin.py
from django.contrib import admin
from import_export.admin import ImportExportModelAdmin
from import_export.fields import Field
from import_export import resources
# from import_export import resources
from .models import Company,Category, CompanyImage
from.resources import CompanyResource
class CompanyResource(resources.ModelResource):
class Meta:
model = Company
class CompanyImageAdmin(admin.StackedInline):
model = CompanyImage
class CompanyAdmin(ImportExportModelAdmin):
resource_class = CompanyResource
inlines = [CompanyImageAdmin]
# Register your models here.
admin.site.register(Category)
admin.site.register(Company,CompanyAdmin)
#admin.register(CompanyImage)
class CompanyImageAdmin(admin.ModelAdmin):
pass
views.py
def importcompanies(request):
if request.method == 'GET':
return render(request, 'prospects/import.html')
else:
file_format = request.POST['file-format']
company_resource = CompanyResource()
dataset = Dataset()
new_companies = request.FILES['myfile']
if file_format == 'CSV':
imported_data = dataset.load(new_companies.read().decode('utf-8'),format='csv')
result = company_resource.import_data(dataset, dry_run=True, raise_errors=True)
elif file_format == 'XLSX':
imported_data = dataset.load(new_companies.read(),format='xlsx')
result = company_resource.import_data(dataset, dry_run=True, raise_errors=True)
elif file_format == 'XLS':
imported_data = dataset.load(new_companies.read(),format='xls')
result = company_resource.import_data(dataset, dry_run=True, raise_errors=True)
if result.has_errors():
messages.error(request, 'Uh oh! Something went wrong...')
else:
# Import now
company_resource.import_data(dataset, dry_run=False)
messages.success(request, 'Your words were successfully imported')
return render(request, 'prospects/import.html')
You have CompanyResource defined in two places, so this could be the source of your problem. Remove the declaration from admin.py and see if that helps.
As you say, fields and exclude are used to define which model fields to import. fields is a whitelist, whilst exclude is a blacklist, so you shouldn't need both.
Set up a debugger (if you haven't already) and step through to find out what is going on (this can save days of effort).
If it is still not working, please update your answer and try to be specific about the nature of the issue (see how to ask).

Boolean Filter on Django Admin

I'm trying to build a filter that corresponds to the has_images method on my Django admin, but I can't because it strictly says that has_images is not a field of the model. I tried setting it up as a property, but it also didn't work.
I thought about defining has_images as a field and really calculating it, based on the changes on the model, but I think that would be not optimal.
What would be a good solution here?
models.py
class Product(models.Model):
name = models.CharField("Name", max_length=255)
def has_images(self):
return self.images.all().count() > 0
has_images.boolean = True
class ProductImage(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="images")
file = models.ImageField("Product Image")
admin.py
class ProductImageInline(admin.TabularInline):
model = ProductImage
fields = ('file',)
extra = 1
class ProductAdmin(VersionAdmin):
list_display = ('id', 'name', 'has_images',)
inlines = (ProductImageInline,)
Expected result:
Can you share the contents of the admin.py file?
Or let me explain it as follows. Add a feature called list_filter = ('images') into the ProductAdmin class you created in admin.py. If this feature doesn't work (I'm not sure as I haven't tried it), if you create an Admin Class for ProductImages directly, you can already view the pictures and the corresponding Product on that page.
----------- EDIT ----------------
This is how I solved the problem.
models.py
from django.db import models
class Product(models.Model):
name = models.CharField("Name", max_length=255)
is_image = models.BooleanField(default=False, editable=False)
def save(self, *args, **kwargs):
if self.images.count():
self.is_image = True
else:
self.is_image = False
super(Product, self).save(*args, **kwargs)
class ProductImage(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="images")
file = models.ImageField("Product Image")
def save(self, *args, **kwargs):
super(ProductImage, self).save(*args,**kwargs)
self.product.save()
admin.py
from django.contrib import admin
from .models import *
class ProductImageInline(admin.TabularInline):
model = ProductImage
fields = ('file',)
extra = 1
class ProductAdmin(admin.ModelAdmin):
list_display = ('id', 'name',)
list_filter = ('is_image',)
inlines = (ProductImageInline,)
admin.site.register(Product, ProductAdmin)
Here I added an is_image BooleanField field with False by default. Every time the save method of the Product model runs, it checks whether there is an image in the ProductImage to which the Product model is attached. If there is an image in it, is_image is set as True.

How to skip an existing object instance when creating resources in bulk python

I am trying to create a resources in bulk. While the resources are created I have the matric_no has to be unique. If the value of an existing matric_no is uploaded together with the some new entries, I get an integrity error 500 because the value already exists and it stops the rest of the values from being created. How can I loop through these values and then check if the value exists, and then skip so that the others can be populated? Here is my code:
**models.py**
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
#python_2_unicode_compatible
class Undergraduate(models.Model):
id = models.AutoField(primary_key=True)
surname = models.CharField(max_length=100)
firstname = models.CharField(max_length=100)
other_names = models.CharField(max_length=100, null=True, blank=True)
card = models.CharField(max_length=100, null=True, blank=True)
matric_no = models.CharField(max_length=20, unique=True)
faculty = models.CharField(max_length=250)
department_name = models.CharField(max_length=250)
sex = models.CharField(max_length=8)
graduation_year = models.CharField(max_length=100)
mobile_no = models.CharField(max_length=150, null=True, blank=True)
email_address = models.CharField(max_length=100)
residential_address = models.TextField(null=True, blank=True)
image = models.CharField(max_length=250,
default='media/undergraduate/default.png', null=True, blank=True)
def __str__(self):
return "Request: {}".format(self.matric_no)
***serializers.py***
from .models import Undergraduate
from .models import Undergraduate
class UndergraduateSerializer(serializers.ModelSerializer):
class Meta:
model = Undergraduate
fields ='__all__'
class CreateListMixin:
"""Allows bulk creation of a resource."""
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
print(list)
kwargs['many'] = True
return super().get_serializer(*args, **kwargs)
**api.py**
from .models import Undergraduate
from rest_framework.viewsets import ModelViewSet
from .serializers import CreateListMixin,UndergraduateSerializer
class UndergraduateViewSet(CreateListMixin, ModelViewSet):
queryset = Undergraduate.objects.all()
serializer_class = UndergraduateSerializer
permission_classes = (permissions.IsAuthenticated,)
**urls.py**
from rest_framework.routers import DefaultRouter
from .api import UndergradMassViewSet
router=DefaultRouter()
router.register(r'ug', UndergradMassViewSet)
This is the updated serializer.py
class UndergraduateSerializer(serializers.ModelSerializer):
class Meta:
model = Undergraduate
fields = ('id', 'surname', 'firstname', 'other_names', 'card','matric_no', 'faculty', 'department_name', 'sex', 'graduation_year', 'mobile_no', 'email_address', 'residential_address')
def create(self, validated_data):
created_ids = []
for row in validated_data:
try:
created = super().create(row)
created_ids.append(created.pk)
except IntegrityError:
pass
return Undergraduate.objects.filter(pk__in=[created_ids])
This is how i moved it now
Seriliazers.py
class UndergraduateSerializer(serializers.ModelSerializer):
def create(self, validated_data):
created_ids = []
for row in validated_data:
try:
created = super().create(row)
created_ids.append(created.pk)
except IntegrityError:
pass
return Undergraduate.objects.filter(pk__in=[created_ids])
class Meta:
model = Undergraduate
fields = ('id', 'surname', 'firstname', 'other_names', 'card','matric_no', 'faculty', 'department_name', 'sex', 'graduation_year', 'mobile_no', 'email_address', 'residential_address')
read_only_fields = ('id',)
When the list sent has an existing matric_no , it returns 500 ListSeriaizer is not iterable
You definitely have to implement a custom create() method in your serializer to handle such a case as the serializer's default create method expects one object.
Nonetheless, there is a couple of design decisions to consider here:
You can use bulk_create but it has a lot of caveats which are listed in the docs and it would still raise an integrity error since the inserts are done in one single commit. The only advantage here is speed
You can loop over each object and create them singly. This will solve the integrity issue as you can catch the integrity exception and move on. Here you'll lose the speed in 1
You can also check for integrity issues in the validate method before even moving on to create. In this way, you can immediately return an error response to the client, with information about the offending ro. If everything is OK, then you can use 1 or 2 to create the objects.
Personally, I would choose 2(and optionally 3). Assuming you also decide to chose that, this is how your serializer's create method should look like:
def create(self, validated_data):
created_ids = []
for row in validated_data:
try:
created = super().create(row)
created_ids.append(created.pk)
except IntegrityError:
pass
return Undergraduate.objects.filter(pk__in=[created_ids])
So in this case, only the created objects will be returned as response