Django last page pagination redirect - django

My problem is similar to this problem. The only difference is I use GCBV for my pagination. My view file is as follows:
class ChatListView(ListView):
model = Chat
form_class = NewMessageForm
template_name = 'chat.html'
paginate_by = 5
queryset = model.objects.all() ###not needed
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
return redirect('chat') <--- here
return render(request, self.template_name, {'form': form})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = NewMessageForm() # edited: self.form_class
return context
What I want the post method to redirect to the last page of the pagination. In the link, it was achieved by return HttpResponseRedirect('/forum/topic/%s/?page=%s' % (topic.slug, posts.num_pages)). But for my GCBV, I don't know how to get the .num_pages via an object. A little help please.

You could call get_context_data, which will paginate the queryset and include paginator in the context. You can then access the number of pages with paginator.num_pages.
from django.urls import reverse
class ChatListView(ListView):
...
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
self.object_list = self.get_queryset() # get_context_data expects self.object_list to be set
context = self.get_context_data()
paginator = context['paginator']
num_pages = paginator.num_pages
return redirect(reverse('chat') + '?page=%s' % paginator.num_pages)

Related

converting CBV to FBV

I am trying to change all my Function Based View to Class based view, i’ve been fairly successful except for this view, it’s a detail view that contains paystack payment gateway. Any help will be hugely appreciated.
def car_rent_detail_view(request, pk):
object = get_object_or_404(CarRent, id=pk)
paystack = PaystackAccount(
settings.PAYSTACK_EMAIL,
settings.PAYSTACK_PUBLIC_KEY,
object.total_cost
)
context = {'object': object, 'pk_public': settings.PAYSTACK_PUBLIC_KEY, 'currency': 'NGN', 'paystack': paystack,
}
if request.method == 'POST':
if paystack.verify_transaction(request.POST['reference']):
messages.success(request, "payment successfull")
…
car_rented.save()
…
rent_activation.save()
messages.success(request, "Rent successfully updated")
return render(request, 'app/CarRent_detail.html', context=context)
I will like to convert the CBV below to FBV so i can add payment functionality to it.
class ContestantDetail(DetailView, FormMixin):
model = Contestant
context_object_name = 'contestants'
template_name = 'contest/contestant_detail.html'
form_class = VoteForm
def get_success_url(self):
return reverse('contest:contestant-detail', kwargs={'pk': self.object.pk})
def get_context_data(self, *args, **kwargs):
context = super(ContestantDetail, self).get_context_data(*args, **kwargs)
context['vote_contestant'] = Contestant.objects.get(pk=self.kwargs.get('pk'))
return context
def post(self, request, *args, **kwargs):
form = self.get_form()
self.object = self.get_object()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form, *args, **kwargs):
contestant = Contestant.objects.get(pk=self.kwargs['pk'])
...
contestant.save()
messages.success(self.request, f'You have successfully casted {vote_count} vote.')
return super().form_valid(form)
The Class based View above can be converted to a Function based view as demonstrated below.
def contestant_detail_view(request, pk):
get_object_or_404(Contestant, pk=pk)
form = VoteForm()
context = {'contestants': get_object_or_404(Contestant, pk=pk),
'vote_contestant': Contestant.objects.get(pk=pk),
'form': form}
if request.method == 'POST':
form = VoteForm(request.POST)
if form.is_valid():
con = Contestant.objects.get(pk=pk)
...
con.save()
else:
form = VoteForm()
return render(request, 'contest/contestant_detail.html', context)

How to get object at CreatView

There is a form that is rendered by url
url(r'kredit/(?P<credit_slug>[-\.\w\d]+)/$', CreditDetail.as_view(), name='credit_detail'),
urls
url(r'kredit/(?P<credit_slug>[-\.\w\d]+)/$', CreditDetail.as_view(), name='credit_detail'),
url(r'kredit_request/$', CreditOnlineRequestView.as_view(), name='credit_request'),
The form is processed in the CreditOnlineRequestView(CreateView) view.
It is necessary to pull out the credit_slug from CreditDetail view in it (here the form was drawn)
​
views
class CreditDetail(FormView):
form_class = CreditPaymentForm
template_name = 'credits/credit_detail.html'
​
def get_initial(self):
initial = super(CreditDetail, self).get_initial()
initial['request'] = self.request
return initial
​
def get(self, *args, **kwargs):
request_form = CreditOnlineRequestForm(self.request.GET or None, prefix="request")
​
​
class CreditOnlineRequestView(CreateView):
form_class = CreditOnlineRequestForm
model = CreditOnlineRequest
template_name = 'credits/credit_listing.html'
prefix = 'request'
​
def form_valid(self, form, **kwargs):
credit_request = form.save(commit=False)
credit_request.credit = credit #???
return super(CreditOnlineRequestView, self).form_valid(form)
​
def form_invalid(self, form):
errors = dict([(k, v[0]) for k, v in form.errors.items()])
return errors
forms
class CreditOnlineRequestForm(forms.ModelForm):
class Meta:
model = CreditOnlineRequest
exclude = ['credit'] #this field must be define
​
def __init__(self, *args, **kwargs):
super(CreditOnlineRequestForm, self).__init__(*args, **kwargs)
#???
What are the options? I think, either through the cache, or through pulling out the previous page to do, but this is somehow not very humane, as for me. The best option, as for me, is to transfer the credit instance to a hidden form field in the CreditDetail view, but I don’t know how to do it yet.
The problem is that internally the form_valid function is doing the following:
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save()
return super().form_valid(form)
So it does not matter what you're doing in your override that the super will try to save the form directly. You can solve your problem by doing:
def form_valid(self, form, **kwargs):
credit_request = form.save(commit=False)
credit_request.credit = credit
credit_request.save()
return HttpResponseRedirect(self.get_success_url())
urls
url(r'kredit_request/(?P<credit_slug>[-\.\w\d]+)/$', CreditOnlineRequestView.as_view(), name='credit_request'),
html
<form action="{% url 'credit_request' credit.slug %}" method="post">
view
class CreditOnlineRequestView(CreateView):
form_class = CreditOnlineRequestForm
model = CreditOnlineRequest
slug_url_kwarg = 'credit_slug'
prefix = 'request'
def post(self, request, *args, **kwargs):
form = self.get_form()
credit = Credit.objects.get(slug=kwargs.get('credit_slug'))
cache.set('credit_for_request', credit)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
credit_request = form.save(commit=False)
credit = cache.get('credit_for_request')
cache.clear()
credit_request.credit = credit
credit_request.save()
return HttpResponseRedirect(reverse('credit_detail', kwargs={'credit_slug': credit.slug}))

Get URL pk in django forms.py

I have this URL
path('private/productores/<pk>', views.Productor_Private.as_view()),
Views.py
class Productor_Private(generic.DetailView):
model = Productor
template_name = 'firstpage/productor_private.html'
def get(self, request, pk):
form = RepartoForm()
return render(request, self.template_name, {'form': form})
def post(self, request, pk):
form = RepartoForm(request.POST)
if form.is_valid():
return render(request, self.template_name, args)
I want to retrieve the pk from the URL to use it as a filter inside the forms.py, to do something like this:
class RepartoForm(forms.Form):
productos = forms.ModelMultipleChoiceField(queryset=Producto.objects.filter(productor=pk))
So in other words, I need to check what the current user's "productor" id is in order to only retrieve the "productos" that belong to this "productor"
You will need to "patch" the form constructor, and manually set the queryset in the __init__ function:
class RepartoForm(forms.Form):
productos = forms.ModelMultipleChoiceField(queryset=Producto.objects.all())
def __init__(self, *args, productor_pk=None, **kwargs):
super(forms.Form, self).__init__(*args, **kwargs)
if productor_pk is not None:
self.fields['productos'].queryset = Producto.objects.filter(
productor=productor_pk
)
Or for older versions of Python that does not implement more advanced parameter unpacking, one can implement it like:
class RepartoForm(forms.Form):
productos = forms.ModelMultipleChoiceField(queryset=Producto.objects.all())
def __init__(self, *args, **kwargs):
productor_pk = kwargs.pop('productor_pk', None)
super(forms.Form, self).__init__(*args, **kwargs)
if productor_pk is not None:
self.fields['productos'].queryset = Producto.objects.filter(
productor=productor_pk
)
In case no product_pk is given, the queryset are all the Productos (in case you do not want that, you can alter the form, and for example by default use an empty QuerySet like Producto.objects.none()).
Then in the view, you can construct the form with a named productor_pk parameter:
class Productor_Private(generic.DetailView):
model = Productor
template_name = 'firstpage/productor_private.html'
def get(self, request, pk):
form = RepartoForm(productor_pk=pk)
return render(request, self.template_name, {'form': form})
def post(self, request, pk):
form = RepartoForm(request.POST, productor_pk=pk)
if form.is_valid():
return render(request, self.template_name, args)
Note: you also need to cover the case where the form is invalid: right now post will return None for that, but you should return a HTTP response for all codepaths.

How to use two different models in CreateView in django

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.

Post request with DJango DetailView give error 'MyView' object has no attribute 'object'

I am trying to create an object with django DetailView. My code is like that.
class Detail(DetailView):
model = MyModel
template_name = 'mymodel_detail.html'
def get_context_data(self, **kwargs):
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = DetailForm
return context
def post(self, request, *args, **kwargs):
form = DetailForm(request.POST, request.FILES)
if form.is_valid():
context['reply_form'] = DetailForm
self.object = super(Detail, self).get_object()
context['object'] = super(Detail, self).get_object()
return self.render_to_response(request=request, template=self.get_template_names(), context=context)
else:
context = context = super(Detail, self).get_context_data(**kwargs)
context['reply_form'] = form
self.object = super(Detail, self).get_object()
context['object'] = super(Detail, self).get_object()
return self.render_to_response(request=request, template=self.get_template_names(), context=context)
But here I am getting error that
'Detail' object has no attribute 'object'
I tried to assign object in context instance and with self as well. But nothing works.
What you are missing here is that you have to assign the object to the class or self before calling the get_context_data().
class Detail(DetailView):
model = MyModel
template_name = 'mymodel_detail.html'
def get_context_data(self, **kwargs):
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = DetailForm
return context
def post(self, request, *args, **kwargs):
form = DetailForm(request.POST, request.FILES)
if form.is_valid():
# Write Your Logic here
self.object = self.get_object()
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = DetailForm
return self.render_to_response(context=context)
else:
self.object = self.get_object()
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = form
return self.render_to_response( context=context)
and in render_to_response() Just pass context. No other arguments.
Hope it will work for you.
This is how I implemented the code from Safrazs answer to make a reply option on my question model. I know this is an old question, but I hope that someone will find this useful.
class QuestionDetailView(generic.DetailView):
model = Question
template_name = 'forum/question.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = ReplyForm
return context
def post(self, request, *args, **kwargs):
form = ReplyForm(request.POST)
if form.is_valid():
reply = form.save(commit=False)
reply.creator = request.user
reply.question = self.get_object()
reply.save()
self.object = self.get_object()
context = context = super().get_context_data(**kwargs)
context['form'] = ReplyForm
return self.render_to_response(context=context)
else:
self.object = self.get_object()
context = super().get_context_data(**kwargs)
context['form'] = form
return self.render_to_response(context=context)
You are inheriting from the wrong generic view. You need to inherit from CreateView, like this:
class CreateModel(CreateView):
model = MyModel
template_name = 'mymodel_detail.html'
form_class = DetailForm
success_url = reverse('/thanks')
def form_valid(self, form):
# this method is called when the form
# is successfully validated
# if you need to do something with
# the database object, this is the place
# do not use it to redirect to a success page
# use the success_url for that
return super(CreateModel, self).form_valid(form)
You are calling super on the wrong class: they should be Detail, not MessageDetail. Also, you don't need the form code. Instead, use one of the generic editting views (CreateView, DeleteView, FormView, UpdateView). The DetailView is only for display purposes really. More detail on the generic views can be found at http://ccbv.co.uk/