Using UpdateModelMixin update() to update record - python

How can I perform a partial update within list()? I am trying to update the balance value in a Wallet record
class WalletListCreateAPIView(generics.ListCreateAPIView, mixins.UpdateModelMixin):
queryset = Wallet.objects.all()
serializer_class = WalletSerializer
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
current_user = request.user
wallets = Wallet.objects.filter(user=current_user)
balances = get_wallet_balances([wallet.address for wallet in wallets])
for wallet in wallets:
# Update wallet balance based on address
balance = balances[wallet.address]
return super().list(request, *args, **kwargs)

You just have to create a put() function inside your view, while inheriting GenericAPIView and UpdateModelMixin
class MyView(GenericAPIView, UpdateModelMixin):
serializer_class = MySerializer
queryset = MyModel.objects.all()
lookup_field = 'id'
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
You should create your serializer to contain only the fields you want to update
serializers.py:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ["field_1", "field_2", "field_3"]
Take into consideration that any field that has null=False in the model will be required and not optional
You can dodge this by adding
extra_kwargs = {"field_x": {"required": False}}
in your Meta class

Related

not working update method in rest API framework

I want to be able to edit registered User profile by using UpdateModelMixin class. The forms to edit is existed but when we want to PUT the new information , the new one is not applied and the pervious info is displayed.
models.py:
class Student(models.Model):
user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
entry_year = models.PositiveIntegerField()
student_no = models.PositiveIntegerField()
def get_full_name(self):
return self.user.first_name + self.user.last_name
def __unicode__(self):
return self.get_full_name()
views.py:
class ProfessorDetailAPIView(DestroyModelMixin, UpdateModelMixin, RetrieveAPIView):
queryset = Professor.objects.all()
serializer_class = ProfessorDetailSerializers
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
serializers.py:
class ProfessorDetailSerializers(serializers.ModelSerializer):
user = CustomUserSerializer()
professor_no = SerializerMethodField()
class Meta:
model = Student
fields = (
'user',
'professor_no',
)
def get_professor_no(self, obj):
return str(obj.professor_no)
There is not any changes applied on information

Django REST Framework Filter Queryset Based On URL

When user visits "baseurl/companies/6/inquiry/", I know that the company_id is 6.
Then user then has the option to create an inquiry with specific products, but should only see the products that belong to company 6.
Here's my viewset:
class InquiryViewSet(viewsets.ModelViewSet):
queryset = Inquiry.objects.all()
serializer_class = InquirySerializer
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(*args, company_id=self.kwargs['company_id'], context=context, **kwargs)
Here's my serializer:
class InquirySerializer(serializers.ModelSerializer):
def __init__(self, *args, company_id=None, **kwargs):
super(InquirySerializer, self).__init__(*args, **kwargs)
company_set = Company.objects.filter(pk=company_id)
self.fields['company'].queryset = company_set
company = serializers.HyperlinkedRelatedField(many=False,
view_name='company-detail',
queryset=Company.objects.all())
inquirer = UserPKField(many=False)
is_anonymous = serializers.BooleanField
product_rows = CompanyProductField(many=True, company_id= 'Want to access company_id in __init__')
class Meta:
model = Inquiry
fields = ('pk', 'company', 'inquirer_email', 'inquirer', 'is_anonymous', 'inquiry_date', 'product_rows')
read_only_fields = ('inquirer', 'inquiry_date')
And here's the CompanyProductField
class CompanyProductField(serializers.PrimaryKeyRelatedField):
def __init__(self, *args, company_id=None, **kwargs):
super(CompanyProductField, self).__init__(*args, **kwargs)
self.company_id = company_id
def get_queryset(self):
product_query = Q(company__pk=self.company_id)
return Product.objects.filter(product_query)
There has to be a simple way I can access the company_id that's already in InquirySerializer's init method and just pass that on, but I'm stumped.
class InquirySerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
company_id = kwargs.pop('company_id')
self.company_id = company_id
super().__init__(*args, **kwargs)
product_rows = CompanyProductField(many=True)
class CompanyProductField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return Product.objects.filter(company_id=self.root.company_id)
The self.root attribute on the CompanyProductField class will refer to the instance of InquirySerializer in this case.
For now, I'm going to use this somewhat "hacky" way of doing this.
In my serializers.py file I added a global variable:
from rest_framework import serializers
from .models import *
from django.db.models import Q
global_company_id = 0
Then in the init method for my serializer I set the global_company_id:
class InquirySerializer(serializers.ModelSerializer):
def __init__(self, *args, company_id=None, **kwargs):
super(InquirySerializer, self).__init__(*args, **kwargs)
company_set = Company.objects.filter(pk=company_id)
self.fields['company'].queryset = company_set
global global_company_id
global_company_id = company_id
company = serializers.HyperlinkedRelatedField(many=False,
view_name='company-detail',
queryset=Company.objects.all())
inquirer = UserPKField(many=False)
is_anonymous = serializers.BooleanField
product_rows = CompanyProductField(many=True)
And in the CompanyProductField I accessed the global_company_id:
class CompanyProductField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
product_query = Q(company__pk=global_company_id)
return Product.objects.filter(product_query)
You could just remove the self from self.kwargs['company_id'] in yourget_serializer()` method,
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(company_id=kwargs['company_id'], *args, **kwargs)

How to set django many-to-many field to accept null

I am working on a python/django application. In my application there are 2 tables Store and Ad. That have many to many relation.
Class Store:
ads = models.ManyToManyField(Ad, null=True, blank=True)
Class Store:
ads = models.ManyToManyField(Ad)
I have tested it with both implementations given above but when i save my store without selecting an ad it gives me error:
ads: This field is required.
How can i set ads optional here???
View:
class StoreView(FormView):
form_class = StoreForm
success_url = "/"
template_name = 'store.html'
def __init__(self):
super(StoreView, self).__init__()
self.store = None
def get_form_kwargs(self):
kwargs = super(StoreView, self).get_form_kwargs()
kwargs['current_user'] = self.request.user
if 'store_id' in self.kwargs:
self.store = Store.objects.get(id=self.kwargs['store_id'])
kwargs['instance'] = self.store
kwargs['request'] = self.request
return kwargs
def get_context_data(self, **kwargs):
context = super(StoreView, self).get_context_data(**kwargs)
context['store_info'] = self.store
return context
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(StoreView, self).dispatch(*args, **kwargs)
def form_invalid(self, form):
return super(StoreView, self).form_invalid(form)
def form_valid(self, form):
self.object = form.save()
return super(StoreView, self).form_valid(form)
Form:
class StoreForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['ads'] = forms.ModelMultipleChoiceField(
queryset=Ad.objects.filter(type=13),
widget=forms.CheckboxSelectMultiple,
label='Ads associated with this store'
)
def save(self, commit=False):
store = super(StoreForm, self).save(commit=True)
return store
class Meta:
model = Store
add required=False in definition ads field in the form. When you override a field in model form, no attributes are inherited from the model. You have to add all constraints to it like max_length, required etc.

Django: Filter ModelChoiceField by user

I have a model along with a ModelForm based on that model. The ModelForm contains a ModelMultipleChoice field, which I specify in the subclass of my ModelForm:
class TransactionForm(ModelForm):
class Meta:
model = Transaction
def __init__(self, *args, **kwargs):
super(TransactionForm, self).__init__(*args, **kwargs)
self.fields['category'] = forms.ModelChoiceField(queryset=Category.objects.filter(user=user))
As you can see, I need to filter the Category queryset by user. In other words, users should only see their own categories on the drop down. But how can I do this when user, or more specifically, request.user, is not available in a Model instance?
Edit: Adding my subclass of the CBV:
class TransUpdateView(UpdateView):
form_class = TransactionForm
model = Transaction
template_name = 'trans_form.html'
success_url='/view_trans/'
def get_context_data(self, **kwargs):
context = super(TransUpdateView, self).get_context_data(**kwargs)
context['action'] = 'update'
return context
I tried form_class = TransactionForm(user=request.user) and I'm getting a NameError saying that request was not found.
You can pass request.user to form init in view:
def some_view(request):
form = TransactionForm(user=request.user)
and add user parameter to form __init__ method (or pop it from kwargs in form):
class TransactionForm(ModelForm):
class Meta:
model = Transaction
# def __init__(self, *args, **kwargs):
# user = kwargs.pop('user', User.objects.get(pk_of_default_user))
def __init__(self, user, *args, **kwargs):
super(TransactionForm, self).__init__(*args, **kwargs)
self.fields['category'] = forms.ModelChoiceField(
queryset=Category.objects.filter(user=user))
update: in class based views you can add extra parameter to form init in get_form_kwargs:
class TransUpdateView(UpdateView):
#...
def get_form_kwargs(self):
kwargs = super(YourView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs

Django REST Framework different depth for POST/PUT?

I am using Django REST Framework to create an API for my web app. I have a class 'Comment', that has depth=2 set in the Meta class. This works great when GETing the Comments. When I try to send a POST or PUT request though (i.e. create a new Comment) I am told I need to include objects instead of ForeignKey IDs.
Here's my Serializer class:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
depth = 2
The model:
class Comment(models.Model):
user = models.ForeignKey(User, null=True, blank=True,
related_name='comments')
budget = models.ForeignKey(Budget, related_name='comments')
published = models.BooleanField(default=False)
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
The view code:
class Comments(generics.ListCreateAPIView):
model = Comment
serializer_class = CommentSerializer
def pre_save(self, obj):
obj.user = self.request.user
And the error that is displayed in the output (JSON) is:
{"user": ["This field is required."], "budget": [{"non_field_errors": ["Invalid data"]}]}
When this raw data is sent:
{"budget": 2, "published": true, "body": "Another comment"}
I know this is a little bit late but I ended up using 2 serializers like so:
class CommentReadSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
depth = 2
class CommentWriteSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
Then used like this:
class Comments(generics.ListCreateAPIView):
model = Comment
serializer_class = CommentReadSerializer
def create(self, request, *args, **kwargs):
serializer = CommentWriteSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
serializer = CommentReadSerializer(serializer.object)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
You can set different serializers by overriding the get_serializer_class() function, like so:
def get_serializer_class(self):
method = self.request.method
if method == 'PUT' or method == 'POST':
return YourWriteSerializer
else:
return YourReadSerializer
I thought to add this one, since i came here from Googling after a while.
I believe the proper way to define a serializer field that refers to a foreign key relationship is through something like serializers.PrimaryKeyRelatedField. I don't believe that model serializers automatically use this field class without defining it explicitly in the serializer class.
http://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
I would imagine that a PrimaryKeyRelatedField serializer would correctly handle JSON data submissions like the one you used in your example.
I had the same problem so I Solved making custom generic methods.This is better implementation of above answers
class CustomListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get_serializer_class(self):
method = self.request.method
if method == 'PUT' or method == 'POST':
return self.writeSerializers
else:
return self.readSerializers
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Similarily RUD,
class CustomRetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
"""
Concrete view for retrieving, updating or deleting a model instance.
"""
def get_serializer_class(self):
method = self.request.method
if method == 'PUT' or method == 'POST':
return self.writeSerializers
else:
return self.readSerializers
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs) # enter code here
Now I just give writeSerializers and readSerializers values in Views.py
Also to create Read-write Serializers there is an easy way.
class employeeWriteSerializer(serializers.ModelSerializer):
class Meta:
model = employee
fields = ('username','email',..)
class employeeReadSerializer(serializers.ModelSerializer):
class Meta(employeeWriteSerializer.Meta):
depth = 1
It saves time and repetitive work you can also add authentication classes in custom generic Api(Retitve work). Thanks.

Categories

Resources