I'm failing to update multiple data in my modelset at once - django-forms

I have pre-populated my table with products data which I then retrieve to edit. I am trying to edit several rows then save it at one go.
I have two models of Grade and Product described below
class Grade(models.Model):
GRADE_TYPES = (
("p", "Primary Grade"),
("S", "Secondary Grade"),
)
grade_name = models.TextField(max_length=10, unique = True)
grade_type = models.CharField(max_length=1, choices=GRADE_TYPES)
class Meta:
ordering = ['grade_name']
def __str__(self):
return self.grade_name
def get_absolute_url(self):
return reverse('grade_list')
class Product(models.Model):
production_date = models.DateField()
grade = models.ForeignKey(Grade, on_delete=models.SET_NULL, null=True)
kilos = models.DecimalField(decimal_places=1, max_digits = 5)
products=[]
class Meta:
unique_together =('production_date','grade')
ordering = ['-production_date']
def __str__(self):
return self.production_date
def get_absolute_url(self):
return reverse('production_list')
My form
class Add_ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
product_date = datetime.today()-timedelta(days=1)
grades = Grade.objects.all()
for grade in grades:
product = Product.create(product_date,grade,0)
product.save()
class Add_product(View):
template_name = 'create_daily_product.html'
Product_Formset = modelformset_factory(Product, form=Add_ProductForm)
def get(self,request, *args, **kwargs):
context ={
'product_form':self.Product_Formset(queryset =
Product.objects.filter(kilos=0.0)),
}
return render(request,self.template_name,context)
def post(self,request, *args, **kwargs):
product_formset = self.Product_Formset(self.request.POST)
if product_formset.is_valid:
for products in product_formset:
products.save() # The Product could not be changed because
the data didn't validate.
return HttpResponseRedirect(reverse("success.url"))
else:
context ={
'product_form':self.Product_Formset(),
}
return render(request,'create_daily_product.html',context)
I populate the model Product with initial data and then retrieve it so that I can change the quantities of kilos to other values. I then edit the kilos successfully but on clicking update to save, I get the error message "The Product could not be changed because the data didn't validate". The form appears to be valid but on starting to save it fails.
I should render the data to edit, update it and then save it. The first two happen successfully, but not the last. I also want to only save the rows where the kilos>0, but cannot test that since the saving fails.

it probably because this line
if product_formset.is_valid:
it supposed to be
if product_formset.is_valid():
also, check the indentation for functions in your model and modelform.
your models should be
class Grade(models.Model):
GRADE_TYPES = (
("p", "Primary Grade"),
("S", "Secondary Grade"),
)
grade_name = models.TextField(max_length=10, unique = True)
grade_type = models.CharField(max_length=1, choices=GRADE_TYPES)
class Meta:
ordering = ['grade_name']
def __str__(self):
return self.grade_name
def get_absolute_url(self):
return reverse('grade_list')
class Product(models.Model):
production_date = models.DateField()
grade = models.ForeignKey(Grade, on_delete=models.SET_NULL, null=True)
kilos = models.DecimalField(decimal_places=1, max_digits = 5)
products=[]
class Meta:
unique_together =('production_date','grade')
ordering = ['-production_date']
def __str__(self):
return self.production_date
def get_absolute_url(self):
return reverse('production_list')
while your modelform:
class Add_ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
......

Related

TypeError: Field 'id' expected a number but got <class 'rest_framework.fields.CurrentUserDefault'>

Seems I need to apply a dot notation to CurrentUserDefault() object, tried .id but failed
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self,obj):
queryset=TagPrivate.objects.filter(user=serializers.CurrentUserDefault) # <--TypeError
return TagPrivateSerializer(queryset).data
models.py
class DotPrivate(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
name = models.CharField(max_length=255)
description = models.TextField(max_length=350, blank=True)
lon = models.CharField(max_length=20)
lat = models.CharField(max_length=20)
rating = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(5.0)])
tag = models.ForeignKey('TagPrivate', on_delete=models.PROTECT)
in the following link in the first answer I found some solution but I do not completly understand it:
'CurrentUserDefault' object has no attribute 'user'
class TagPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = TagPrivate
fields = ('id', 'name')
read_only_fields = ('id',)
You can not use CurrentUserDefault, this is just a value that the Django serializer will use for a default=… parameter, and then later swap for the request user.
You can fetch this from the request in the context, so:
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self, obj):
queryset = TagPrivate.objects.filter(user=self.context['request'].user)
return TagPrivateSerializer(queryset).data
In the ModelViewSet, you will need to pass the user, so:
class DotPrivateViewSet(ModelViewSet):
queryset = # …
permission_classes = # …
serializer_class = DotPrivateSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update(request=self.request)
return context

Should serializer be called multiple times?

So I have fairly many relations in models and now I'm trying to optimize db queries. One of the models i'm serializing is Thread model:
class Thread(models.Model):
participants = models.ManyToManyField(User, related_name='threads')
is_multi_participants = models.BooleanField(default=False)
creator = models.ForeignKey(User, verbose_name=_('creator'), on_delete=models.CASCADE)
last_sender = models.ForeignKey(User, verbose_name=_('last sender'), related_name='last_sender', on_delete=models.CASCADE)
prevent_reply = models.BooleanField(default=False, editable=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
title = models.CharField(max_length=1000, blank=True, null=True)
company = models.ForeignKey(to=Company, verbose_name=_('Company'),
on_delete=models.SET_NULL, null=True, blank=True)
class ThreadListSerializer(serializers.ModelSerializer):
subtitle = serializers.SerializerMethodField()
class Meta:
model = Thread
fields = ('id',
'subtitle',
)
def get_subtitle(self, obj):
print(traceback.extract_stack(None, 2)[0][2], f' {len(connection.queries)}')
return ''
class ThreadList(generics.ListAPIView):
pagination_class = ThreadPagination
def get_queryset(self):
**queryset filtering goes here**
return queryset
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = ThreadListSerializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
Both view and serializer is simplified to debug one particular problem: while running this code in Pycharm (didn't matter in Debug or Run options) and making request from shell via curl, there are multiple calls of get_subtitle() function, thus resulting in many prints of to_representation *queries_number* to console. So the question is: is this a desired behavior? Should serializer be called as many times as queryset length?

Update method of serializer only creates and doesn't update (Django rest framework)

I'm having a hard time with nested serializers, especially updating them. So far I can update the lesson description and all the main fields before the nested serializer (like course description, title etc). Ideally I'd like to search using lesson_id, and not lesson.title like it is now, and update description and title at the same time. Is there a workaround for that?
My models.py
class Course (models.Model):
title = models.CharField (max_length=150)
description = models.CharField(max_length=250, default="No Description")
student = models.ManyToManyField(Student, related_name='courses', blank=True)
teacher = models.ManyToManyField(Teacher, related_name='teacher', blank=True)
def __str__(self):
return self.title
class Lesson (models.Model):
title = models.CharField(max_length=100)
description = models.TextField(default="No Description")
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='lessons')
def __str__(self):
return self.title
Serializers.py where the problem is
class CourseSerializer (serializers.ModelSerializer):
lessons = LessonSerializer(many=True, required=False)
#teacher = TeacherSerializer(many=True)
class Meta:
model = Course
fields = ('id', 'title', 'description', 'lessons') #to separate serializer with students for teachers later
def update(self, instance, validated_data):
lessons = validated_data.pop('lessons', [])
instance = super().update(instance, validated_data)
for lesson in lessons:
lesson, updated = Lesson.objects.update_or_create( defaults={'description': lesson["description"]}, title= lesson["title"])
#pk = instance.lessons_id doesn't work, I cannot get this id
instance.save()
return instance
Views
class CourseDetailDeleteView (generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAdminUserOrAuthenticatedOrReadOnly,)
queryset = Course.objects.all()
serializer_class = CourseSerializer
def update(self, request, *args, **kwargs):
serializer = CourseSerializer(instance=self.get_object(), data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
If your problem is that validated_data["lessons"][0]["id"] doesn't exists:
By default the id field of a ModelSerializer is read_only, so id is not included in validated_data in .create() or .update(). Thus you'll have to override that:
class LessonSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
....
## Optional: in case you don't want `id` getting explicitly set
def create(self, validated_data):
validated_data.pop("id", None)
return super().create(self, validated_data)
## Optional: in case you don't want `id` of the instance getting updated
def update(self, instance, validated_data):
validated_data.pop("id", None)
return super().update(self, instance, validated_data)
Another note:
There's no way to know if serializer.save() is even called in CourseDetailDeleteView.update. I would suggest to set serializer.is_valid(raise_exception=True) instead so it can return error messages and show you why it didn't save.

AttributeError: 'collections.OrderedDict' object has no attribute 'model_id' and 'model_id' is missing from visible fields

Something strange happened: I was defining an endpoint and initially two fields were visible in the API form: model_id and payload, as given in the model definition:
### models.py:
class CarModel(models.Model):
model_id = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class Calculator(models.Model):
model = models.ForeignKey(CarModel, on_delete=models.CASCADE)
payload = models.TextField()
def model_id(self):
return self.model.model_id
def __str__(self):
return f"Calculations for {self.model.name}"
### serializers.py:
class CalculatorSerializer(serializers.ModelSerializer):
model_id = serializers.SerializerMethodField()
class Meta:
model = Calculator
fields = ['model_id', 'payload']
def get_model_id(self, obj):
return obj.model_id()
### views.py:
class CalculatorViewSet(viewsets.ModelViewSet):
serializer_class = CalculatorSerializer
queryset = Calculator.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(f"{serializer.data.upper()}", status=status.HTTP_200_OK)
So, both fields were visible, but POST requests ended in the AttributeError: 'collections.OrderedDict' object has no attribute 'model_id'. Trying to fix that, I eventually and accidentally removed model_id from view - it doesn't display in DRF's forms. And the AttributeError still persists.
What is wrong with this piece of code?
OK, it turns out that defining fields in this manner:
fields = '__all__'
makes also the model_id visible. Still, no idea why explicit insert doesn't work.
In case of the other issue, the AttributeError, I had to pull the value out of an OrderedDict. Modified method looks like this:
def get_model_id(self, obj):
return obj["model"].model_id
Beside that, I found one more error inside views.py's create method: serializer.data won't implement upper() method; some key, in my case serializer.data['payload'], has to be referenced, so for example:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
res = {
"payload": f"{serializer.data['payload'].upper()}"
}
return Response(res, status=status.HTTP_200_OK)

django rest_framework serializer parameter

I am using django rest_framework to provide jsGrid json data.
As the rest_framwork example, I create a object fit the jsGrid format
class jsGridResp(object):
def __init__(self, data, itemsCount):
self.data = data
self.itemsCount = itemsCount
and the class based view, create a get function
class RateListViewSet(mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
"""
API endpoint that allows user to be viewed or edited
"""
queryset = RateList.objects.all().order_by('-create_date')
serializer_class = RateListSerializer
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
obj = queryset.get(pk=self.request.POST["id"])
self.check_object_permissions(self.request, obj)
return obj
def get(self, request, format=None):
pageIndex = request.GET.get('pageIndex')
pageSize = request.GET.get('pageSize')
sortField = request.GET.get('sortField', 'id')
sortOrder = request.GET.get('sortOrder', 'asc')
sortOrder = "" if sortOrder == "asc" else "-"
rows = RateList.objects.all().order_by("{}{}".format(sortOrder, sortField))
itemsCount = rows.count()
paginator = Paginator(rows, pageSize)
try:
rows = paginator.page(pageIndex)
except PageNotAnInteger:
rows = paginator.page(1)
except EmptyPage:
rows = paginator.page(paginator.num_pages)
result = jsGridResp(data=rows, itemsCount=itemsCount)
serializer = RateListGetSerializer(result)
json = JSONRenderer().render(serializer.data)
return Response(json)
then I create two serializer to serialize the data
class RateListSerializer(serializers.ModelSerializer):
class Meta:
model = RateList
fields = ('id', 'rate_code', 'hr01', 'hr02', 'hr03', 'hr04', 'hr05', 'hr06',
'hr07', 'hr08', 'hr09', 'hr10', 'hr11', 'hr12', 'hr13', 'hr14',
'hr15', 'note', 'create_date', 'update_date')
read_only_fields = ('create_date', 'update_date')
def update(self, instance, validated_data):
result = instance.update(id=instance.id, **validated_data)
return result
def destroy(self, instace, validated_data):
return "{seccuess: true}"
class RateListGetSerializer(serializers.Serializer):
itemsCount = serializers.IntegerField()
data = RateListSerializer(many=True)
but I have many model need to do like these.
can I use just one serializer to serialize all model.
I want to create a jsGridGetSerializer can pass in a model parameter, so I don't need to create many simple serialzer to do the same thing.
Is this possible?
If I understand your problem correctly, you want a generic serializer which accepts a Model variable as a parameter in its Meta class.
One way to do this is to pass in the model name as a url keyword argument, then catching it in views.py. Then you can override the Meta model via overriding the get_serializer_class:
serializers.py
class GenericSerializer(serializers.ModelSerializer):
class Meta:
model = None
views.py
class GenericViewSet(viewsets.ModelViewSet):
def get_queryset(self):
model = self.kwargs.get('model')
return model.objects.all()
def get_serializer_class(self):
GenericSerializer.Meta.model = self.kwargs.get('model')
return GenericSerializer

Resources