I have a CreateView in which I need to output data from an object. It works fine with this code below
class MyCreateView(CreateView):
model = ModelName
def dispatch(self, request, *args, **kwargs):
self.project = get_object_or_404(Project, slug=kwargs['project'])
return super(MyCreateView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs):
context_data = super(MyCreateView, self).get_context_data(*args, **kwargs)
context_data.update({'object': self.project})
return context_data
def form_valid(self, form):
obj = form.save(commit=False)
obj.project = self.project
obj.save()
return HttpResponseRedirect(self.get_success_url())
but I was wondering if it's possible to replace the dispatch() and get_context_data() with a get_object(). I've tried the code below, but it does not give me the project as object.
class MyCreateView(CreateView):
model = ModelName
def get_object(self, queryset=None):
return = Project.objects.filter(slug=self.kwargs['project'])
def form_valid(self, form):
obj = form.save(commit=False)
obj.project = self.object
obj.save()
return HttpResponseRedirect(self.get_success_url())
As their respective names imply, the get_object method returns a single object to be manipulated by the view, while get_context_data returns a dict to be displayed in the template. So no, you cannot replace one with the other, and your original code is perfectly fine.
Related
I have following models:
class BankAccount(models.Model):
owner = models.ForeignKey(User)
class MoneyTransfer(models.Model):
sender = models.ForeignKey(BankAccount)
and url:
url(r'^accounts/(?P<pk>\w+)/send_transfer$', SendTransfer.as_view(), name='SendTransfer')
that means "I want to send money from Account with id=pk"
This is my view:
class SendTransfer(View):
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def get(self, request, *args, **kwargs):
instance = BankAccount.objects.get(id=self.kwargs['pk'])
if instance.is_legal():
if instance.organization.owners.all().filter(user__id=self.request.user.id).count() == 0:
return None
else:
if instance.citizen.user.id != self.request.user.id:
return None
return render(self.request, self.template_name, self.get_context_data())
def post(self, request, *args, **kwargs):
sender = BankAccount.objects.get(id=kwargs['pk'])
form = self.form_class(sender, self.request.user, request.POST)
if form.is_valid():
MoneyTransfer.objects.create(sender=sender,
receiver=BankAccount.objects.get(id=self.request.POST['receiver']),
total=float(self.request.POST['total']),
when=timezone.localtime(timezone.now()),
comment=self.request.POST['comment'])
return redirect('AccountDetail', kwargs['pk'])
data = self.get_context_data()
data['form'] = form
return render(request, self.template_name, data)
def get_context_data(self):
account = BankAccount.objects.get(id=self.kwargs['pk'])
return {'form': SendTransferForm(account, self.request.user),
'user': self.request.user,
'account': account}
I think there's a lot of redudant code for CBV. What can I do for shorting it?
UPD
my current code:
class SendTransfer(SingleObjectMixin, FormView):
model = BankAccount
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SendTransfer, self).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = super(SendTransfer, self).get_object(queryset)
if obj.is_legal():
if not obj.organization.owners.filter(user=self.request.user).exists():
raise Http404
else:
if obj.citizen.user != self.request.user:
raise Http404
return obj
def form_valid(self, form):
data = form.cleaned_data
MoneyTransfer.objects.create(sender=self.object,
receiver=data['receiver'], # ModelChoiceField in the form
total=data['total'], # FloatField in the form, etc.
when=timezone.localtime(timezone.now()),
comment=data['comment'])
return redirect('AccountDetail', self.object.pk)
last line of dispatch() method raises TypeError: init() takes at least 3 arguments (1 given)
CBVs are designed for code reuse. If you don't yet have another class that could benefit of code you posted, the actual amount of code is almost identical, be that a CBVs or a plain function.
But the more pythonic and Django-ish (from my biased POV) way would be to:
Inherit your class from the FormView instead of the View. That eases the form management a bit.
Add a SingleObjectMixin to get the object from url kwargs for free.
Move your object validation to the get_object() method. It's a good practive to raise 404 if your object doesn't validate.
Refactor out the get_context_data() as you already have all that data in your context (request, form and object)
Instead of relying on the self.request.POST, clean your data through the form.
class SendTransfer(SingleObjectMixin, FormView):
model = BankAccount
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SendTransfer).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = super(SendTransfer, self).get_object(queryset)
if obj.is_legal():
if not obj.organization.owners.filter(user=self.request.user).exists():
raise Http404
else:
if obj.citizen.user != self.request.user:
raise Http404
return obj
def form_valid(self, form):
data = form.cleaned_data
MoneyTransfer.objects.create(sender=self.object,
receiver=data['receiver'], # ModelChoiceField in the form
total=data['total'], # FloatField in the form, etc.
when=timezone.localtime(timezone.now()),
comment=data['comment'])
return redirect('AccountDetail', self.object.pk)
Some of your code has gone thanks to the CBV magic, some just has moved to another methods. Take a look, I'd welcome your comments.
There is my view:
class SendTransfer(SingleObjectMixin, FormView):
model = BankAccount
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SendTransfer, self).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = super(SendTransfer, self).get_object(queryset)
if not obj.is_owner(self.request.user.citizen):
raise Http404
return obj
def form_valid(self, form):
data = form.cleaned_data
MoneyTransfer.objects.create(sender=self.object,
receiver=data['receiver'], # ModelChoiceField in the form
total=data['total'], # FloatField in the form, etc.
when=timezone.localtime(timezone.now()),
comment=data['comment'])
return redirect('AccountDetail', self.object.pk)
def form_invalid(self, form):
return render(self.request, self.template_name, self.get_context_data())
def get_form_kwargs(self):
return {'sender': BankAccount.objects.get(id=self.kwargs['pk']), 'user': self.request.user}
when form is submitting - I'm getting the same result as after get. Debugger says that clean() is not called but form_invalid is works. What is the problem?
You have overridden get_form_kwargs, and now you are no longer passing data to the form. Without data, the form is unbound, so will never be valid.
It would be better to call super() first, update the kwargs, then return them.
def get_form_kwargs(self):
kwargs = super(SendTransfer, self).get_form_kwargs()
kwargs['sender'] = BankAccount.objects.get(id=self.kwargs['pk']),
kwargs['user'] = self.request.user
return kwargs
I have following code where I want to update another table from an updateView. I am using following code however in method - form_valid its ending up creating new records and not updating the record being edited.
form.instance = form.save(commit=False)
super(AssetInfoUpdateView, self).form_valid(form)
I am using get_context_data method to add context data that I can use in form_valid to update the "other table".
Can anyone point me how to update the current record in form_valid or what I am doing wrong? I have included relevant code from my views.py - for what its worth similar approach in Create is working great, but I can't seem to figure out how to get Update to work.
class AssetInfoUpdateView(LoginRequiredMixin, UpdateView):
model = AssetInfo
context_object_name = 'asset_info'
success_url = '/asset/list/'
template_name = 'asset_mgmt/assetinfo_update.html'
form_class = AssetInfoUpdateForm
def get_context_data(self, **kwargs):
context = super(AssetInfoUpdateView, self).get_context_data(**kwargs)
alarm_dispatch_current = AlarmDispatchInfo.objects.filter(asset=self.object)
alarm_dispatch = AlarmNotificationReceiverInfo.objects.all()
context['alarm_dispatch_current'] = alarm_dispatch_current
context['alarm_dispatch'] = alarm_dispatch
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super(AssetInfoUpdateView, self).get(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = AssetInfo.objects.get(pk=self.kwargs['pk'])
return obj
def form_valid(self, form,request):
clean = form.cleaned_data
form.instance = form.save(commit=False)
super(AssetInfoUpdateView, self).form_valid(form)
....
...
def post(self, request, *args, **kwargs):
form = AssetInfoUpdateForm(request.POST)
if( form.is_valid() ):
return self.form_valid(form,request)
else:
return self.form_invalid(form)
Works like a charm:
MyCreateView(CreateView):
template_name = "my_template_name"
form_class = MyModelForm
success_url = "/success/"
But the following doesn't:
MyUpdateView(UpdateView):
template_name = "my_template_name"
form_class = MyModelForm
success_url = "/success/"
I get this error:
MyUpdateView is missing a queryset. Define MyUpdateView.model, MyUpdateView.queryset, or override MyUpdateView.get_queryset().
Why does an UpdateView need model, queryset or get_queryset defined to not cause an error while CreateView doesn't? Shouldn't it be able to automatically derive it from the Model used in the ModelForm?
Currently (django 1.5.1 official release) UpdateView is calling self.get_object() to be able to provide instance object to Form.
From https://github.com/django/django/blob/1.5c2/django/views/generic/edit.py#L217:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).post(request, *args, **kwargs)
And self.get_object method needs one of this properties declared: model, queryset or get_queryset
Whereas CreateView don't call self.get_object().
From https://github.com/django/django/blob/1.5c2/django/views/generic/edit.py#L194:
def get(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).post(request, *args, **kwargs)
You might have a problem in your urls.py file.
What I think you wrote in it is:
url(r'foldername/(?P[0-9]+)/$', views.UpdateView.as_view(), name='update'),
but you have to change UpdateView to MyUpdateView, like this:
url(r'foldername/(?P[0-9]+)/$', views.MyUpdateView.as_view(), name='update'),
So I have a model that includes:
class Place(models.Model):
....
created_by = models.ForeignKey(User)
My view is like so:
class PlaceFormView(CreateView):
form_class = PlaceForm
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(PlaceFormView, self).dispatch(*args, **kwargs)
Is there a way for me to access request.user and set created_by to that user? I've looked through the docs, but can't seem to find any hints toward this.
`
How about overriding form_valid which does the form saving? Save it yourself, do whatever you want to it, then do the redirect.
class PlaceFormView(CreateView):
form_class = PlaceForm
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(PlaceFormView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
obj.save()
return http.HttpResponseRedirect(self.get_success_url())
I know that this is old, but for other people with this problem:
There is an even simpler way - since saving a form multiple times will always use the same model instance, you can also do:
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
return super(PlaceFormView, self).form_valid(form)
That way, you get all the benefits of the super call - it's trivial to see that you're really only adding those two lines of code, and you don't have to repeat yourself by replicating the redirect logic.
An alternate way to do this is to pass the user through overwriting the get_initial() method in the CreateView, and modify save method in the PlaceForm class to save the user:
class PlaceForm(forms.ModelForm):
...
...
...
def __init__(self, *args, **kwargs):
self.created_by = kwargs['initial']['created_by']
super(PlaceForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
obj = super(PlaceForm, self).save(False)
obj.created_by = self.created_by
commit and obj.save()
return obj
class PlaceFormView(CreateView):
...
...
form_class = PlaceForm
def get_initial(self):
self.initial.update({ 'created_by': self.request.user })
return self.initial
This way the saving logic is still encapsulated within the form class.