How can I perform some logic prior to saving a record within my generic view? I believe the actual saving logic occurs within super().create().
The request within create() looks like this
<QueryDict: {'csrfmiddlewaretoken': ['5WvMZnoBMCUjmlMBaacLnx6Pxt3jUDvHWHvo90ORumYrClkebcx7NJZpmWASRIyG'], 'user': ['1'], 'address': ['3E8ociqZa9mZUSwGdSmAEMAoAxBK3FNDcd']}>
view
class WalletListCreateAPIView(generics.ListCreateAPIView):
queryset = Wallet.objects.all()
serializer_class = WalletSerializer
def create(self, request, *args, **kwargs):
# Some logic here prior to saving
return super().create(request, *args, **kwargs)
For instance, I would like to create the value for balance instead of relying on the value from the request
class Wallet(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
address = models.CharField(max_length=34)
balance = models.DecimalField(default=0, max_digits=16, decimal_places=8)
slug = models.SlugField(max_length=34, blank=True, null=True)
This is the flow when you are saving your request data into model
Also please check the syntax
create -> perform_create -> serializer's create back to perform create then back to create
class WalletListCreateAPIView(generics.ListCreateAPIView):
queryset = Wallet.objects.all()
serializer_class = WalletSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
wallet = self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
self.get_response_data(user),
status=status.HTTP_201_CREATED,
headers=headers,
)
def perform_create(self, serializer):
wallet = serializer.save(user=self.request.user) # if you want to change how you want to save from serializer to your model then you should override create method of serializer as I have shown below
wallet.balance = 30
wallet.save()
return wallet
# serializers.py
class WalletSerializer(serializers.ModelSerializer):
class Meta:
model = Wallet
fields = "__all__"
def create(self, validated_data):
# here in validated data you will receive your request data after validation If you want to discard any request value you can do here
balance = validated_data.pop("balance", None)
wallet = Wallet.objects.create(**validated_data)
return wallet
You can override model save method
class Wallet(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
address = models.CharField(max_length=34)
balance = models.DecimalField(default=0, max_digits=16, decimal_places=8)
slug = models.SlugField(max_length=34, blank=True, null=True)
def save(self, *args, **kwargs):
if not self.pk:
#You can write own logic here
self.balance = 10
super(Wallet, self).save(*args, **kwargs)
I understand your problem.
you want to override create method in generics.ListCreateAPIView.
so you have to define some method, here you want to create new record so you want to define post method and in post method you can override create method.
class WalletListCreateAPIView(generics.ListCreateAPIView):
queryset = Wallet.objects.all()
serializer_class = WalletSerializer
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
NOTE:
- as per your Wallet model address field is not blank, so you should have pass address. then run it.
Related
These are the demo models
class Author(models.Model):
name = models.CharField(max_lenght=5)
class Post(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_lenght=50)
body = models.TextField()
And the respective views are
class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostStatSerializer
I am trying to perform an update/put action on PostViewSet and which is succesfull, but I am expecting different output. After successful update of Post record, I want to send its Author record as output with AuthorSerializer. How to override this and add this functionality?
You can override update method for this:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostStatSerializer
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
# this will return autor's data as a response
return Response(AuthorSerializer(instance.parent).data)
I figured out some less code fix for my issue.
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostStatSerializer
def update(self, request, *args, **kwargs):
super().update(request, *args, **kwargs)
instance = self.get_object()
return Response(AuthorSerializer(instance.author).data)
I don't feel like I get an idea on how it should work...
I do have 2 models:
Group is my custom model I wanna save.
class Group(models.Model):
name = models.CharField(max_length=40)
subject = models.CharField(max_length=40)
user = models.ForeignKey('User', on_delete=models.CASCADE)
User is a standard user model for the application. I'm skipping UserManager for now. Simply said User can have multiple groups.
class User(AbstractBaseUser, PermissionsMixin):
name = models.CharField(max_length=40, null=True, blank=True)
surname = models.CharField(max_length=40, null=True, blank=True)
objects = UserManager()
A serializer for the custom model:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('id', 'name', 'subject', 'user')
And a viewset with overwritten create method:
class GroupViewSet(viewsets.ModelViewSet):
serializer_class = GroupSerializer
def create(self, request, *args, **kwargs):
group = group.objects.create(user=self.request.user)
serializer = GroupSerializer(group)
return Response(serializer.data)
When calling a POST a new Group is created. The Group has relation to the User, but other fields (name, subject) are empty.
On the other hand, when doing the request serialization, the User on the object is empty.
def create(self, request, *args, **kwargs):
serializer = GroupSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
How do I connect those 2 to make it work?
That makes sense since you never used the serializer to deserialize the request, and thus read the details passed in the POST request. You can work with:
def create(self, request, *args, **kwargs):
serializer = GroupSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=self.request.user)
else:
# return response when the serializer rejects the input
pass
# return a response
pass
In the serializer you mark the user field as read_only, to prevent the serializer from failing in case the user was not part of the request:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('id', 'name', 'subject', 'user')
read_only_fields = ('user', )
Any ideas why PATCH returns in response correct data which I would like to save but it doesn't save this data in database?
from .serializers import ObjectSerializer
#permission_classes([UserPermission])
class ObjectDetail(GenericAPIView):
serializer_class = ObjectSerializer
def patch(self, request, object_id):
try:
object = Object.objects.get(id=object_id)
except Object.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = ObjectSerializer(object, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py:
class ObjectSerializer(serializers.ModelSerializer):
class Meta:
model=Object
fields = '__all__'
models.py:
class Object(models.Model):
field1 = models.CharField(max_length=200)
field2 = models.ForeignKey('auth.User')
field3 = models.FloatField()
field4 = models.FloatField(null=True, blank=True)
def save(self, *args, **kwargs):
if not self.pk:
self.field4 = self.field3
super(Object, self).save(*args, **kwargs)
UPDATE
I found the reason why my PATCH method doesn't work. The problem is with def save(self, *args, **kwargs): function in model. When I comment it everything works fine. Any ideas how can I solve it?
I have two models (OK 3 models since AssignedAsset is a subclass of Asset), one that tracks assets and another that tracks the history of owners for that asset. When I create a new asset using CreatView I would like to automatically have it create a History record as well.
models.py
class Asset(models.Model):
make = models.CharField(max_length=100)
model = models.CharField(max_length=100)
serial_number = models.CharField(max_length=100)
po = models.ForeignKey('purchaseorders.PurchaseOrder', default=None, blank=True, null=True)
location = models.ForeignKey('locations.Plant')
slug = models.SlugField(blank=True, unique=True)
def __str__(self):
return self.slug
def save(self):
forslug = "{0.make}-{0.model}-{0.serial_number}".format(self)
self.slug = slugify(forslug)
super(Asset, self).save()
class AssignedAsset(Asset):
user = models.ForeignKey(User)
def __str__(self):
return self.slug
class AssignedHistory(models.Model):
assset = models.ForeignKey('Asset')
user = models.ForeignKey(User)
date = models.DateField()
slug = models.SlugField(blank=True, unique=True)
def __str__(self):
return self.slug
def save(self):
forslug = "{0.asset}-{0.date}".format(self)
self.slug = slugify(forslug)
super(AssignedHistory, self).save()
Here is my view.
class NewAssignedAsset(CreateView):
form_class = AssignedAssetForm
template_name = 'createassignedasset.html'
success_url = '/assets'
And my forms.py
class AssignedAssetForm(forms.ModelForm):
class Meta:
model = AssignedAsset
fields = ['make', 'model', 'serial_number', 'location', 'user', 'po']
def __init__(self, *args, **kwargs):
super(AssignedAssetForm, self).__init__(*args, **kwargs)
#Filter out PO's that have packingslips (otherwise you will quickly have a ridicously big drop-down of every PO in the system)
self.fields['po'] = forms.ModelChoiceField(required=False, queryset=PurchaseOrder.objects.filter(packing_slip=''))
I thought maybe I could have it create the history when it gets the success URL, so I tried this in my view:
import time
def today():
return time.strftime ("%m/%d/%Y")
class NewAssignedAsset(CreateView):
form_class = AssignedAssetForm
template_name = 'createassignedasset.html'
def get_success_url(self):
history = AssignedHistory.objects.create(assset=self.object, user=self.object.user, date=today())
return '/assets'
But this throws a TypeError:
save() got an unexpected keyword argument 'force_insert'
Anything that would point me in the right direction would be appreciated.
You can do it at multiple levels(DB level, form level).
In your case, I'll say you just need to override the save() of your AssignedAssetForm. (Assuming you set user in context of form)
def save(self, *args, **kwargs):
assigned_asset = super(AssignedAssetForm, self).save(*args, **kwargs)
user = self.context.get(u'user')
if user:
assigned_asset_history = AssignedHistory(asset=assigned_asset, user=user, date=datetime.date.today())
assigned_asset_history.save()
return assigned_asset
** I am not sure about the context part, you may have to look into how to use user in form.
You should write your Asset.save() and AssignedHistory.save() as:
def save(self, **kwargs):
...
super(YourModel, self).save(**kwargs)
...
Note the **kwargs. They allow you to accept optional parameters (and a Model.save() has a few).
I've implemented a form where I require fields in the User object to be populated (firstname, lastname, email) as well as fill out a new membership object. I've implemented this with a Function Based View (FBV) but I feel like I should be able to do this with a Class Based View (CBV). The heart of the problem seems to be referencing the current user in a form without passing in the user object. In FBV it's easy to do but I can't find any examples using CBV. I'm thinking that I must be missing something here.
Here is my code
models.py
class Membership(models.Model):
"""Represents an active membership of a user. Both the start_date and
end_date parameters are inclusive."""
DEFAULT_DURATION = 365 # the default number of days a membership is active
start_date = models.DateField(auto_created=True)
end_date = models.DateField(null=True)
membership_type = models.ForeignKey(MembershipType)
referral = models.ForeignKey(User, related_name='membership_referral', null=True)
# Contact Info
phone = PhoneNumberField()
# Address Fields
address_1 = models.CharField(max_length=255)
address_2 = models.CharField(max_length=255, blank=True)
city = models.CharField(max_length=64)
state = USStateField()
zip_code = USPostalCodeField()
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
"""Overload the save function to set the start and end date."""
self.start_date = datetime.date.today()
self.end_date = (self.start_date +
datetime.timedelta(days=self.membership_type.period))
super().save()
#property
def is_active(self):
return self.end_date >= datetime.date.today()
forms.py
class MembershipForm(ModelForm):
"""The Form shown to users when enrolling or renewing for membership."""
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
_fields = ('first_name', 'last_name', 'email',)
_initial = model_to_dict(self.user, _fields) if self.user is not None else {}
super(MembershipForm, self).__init__(initial=_initial, *args, **kwargs)
self.fields.update(fields_for_model(User, _fields))
self.fields['referral'].required = False
class Meta:
model = Membership
fields = ['membership_type', 'referral', 'phone', 'address_1',
'address_2', 'city', 'state']
zip_code = USZipCodeField(max_length=5, required=True)
def save(self, *args, **kwargs):
self.user.first_name = self.cleaned_data['first_name']
self.user.last_name = self.cleaned_data['last_name']
self.user.email = self.cleaned_data['email']
self.user.save()
profile = super(MembershipForm, self).save(*args, **kwargs)
return profile
views.py
#login_required
def enroll(request):
template_name = 'enroll.html'
if request.method == 'POST':
form = MembershipForm(request.POST, user=request.user)
if form.is_valid():
form.save()
return redirect('/')
else:
form = MembershipForm(user=request.user)
return render(request, template_name, {'form': form})
You can access current user in class based view by self.request.user. It can be set in FormView by redefining validate method like this:
class YourView(CreateView)
...
def form_valid(self, form):
form.instance.user = self.request.user
return super(YourView, self).form_valid(form)
I have used CreateView instead of FormView in example because for edit you should check current instance's user in additional for security purposes.
Although your question mentions CBV, yet in the code you are using FBV.
In FBV you have access to request variable being passed. You can use request.user in this case.
In case of CBVs, Django allows you to access request object as self.request. In the implementation of default 'django.views.generic.base.View' class of CBV, they do this as first thing.
Check 4th line of def view as part of as_view in this code - https://github.com/django/django/blob/master/django/views/generic/base.py
All the objects, including user as part of request can be accessed as self.request.user.