Django - dynamic success_url in UpdateView - django

I want my user to be able to see a page, update that page, and then be returned to that page or continue making more edits.
Here's the view to show the information:
# urls.py
url(r'^gameview/$', views.GameView.as_view(template_name='matchview.html'), name='GameView')
# Views.py
class GameView(generic.TemplateView):
template_name = "matchview.html"
def get_context_data(self, **kwargs):
context = super(GameView, self).get_context_data(**kwargs)
q = self.request.GET.get('match')
context['report'] = GameNotes.objects.filter(game=q)
context['game'] = Schedule.objects.get(match=q)
context['gamepic'] = Photo.objects.filter(game=q)
return context
So now they want to add information about a game. I use an UpdateView
class GameView(generic.TemplateView):
template_name = "matchview.html"
def get_context_data(self, **kwargs):
context = super(GameView, self).get_context_data(**kwargs)
q = self.request.GET.get('match')
context['report'] = GameNotes.objects.filter(game=q)
context['game'] = Schedule.objects.get(match=q)
context['gamepic'] = Photo.objects.filter(game=q)
return context
When the user finishes updating in the latter view, they should be returned to the former on the exact same team. This post helped me move in the right direction insofar as using 'get_success_url', but I'm still stuck because I don't think I'm using kwargs. Everything I've tried has resulted in errors.
(My (gulp) thought is that I should re-write the urls to use PKs so that this stuff is easier, but I wanted to make sure)
EDIT:
My fugly attempt (Note: I Have two submit buttons, one to update and one to update and add notes).
def form_valid(self, form):
if form.is_valid():
form.save()
if 'submit' in self.request.POST:
q = self.request.GET.get('match')
return reverse_lazy('TeamView', args=(q))
else:
return render('addnotes', {'game' : q})
SOLUTION:
Learned how to use URL Parameters and kwargs:
(for anyone new like me, self.kwargs.get is brilliant)
def get_success_url(self, **kwargs):
q = self.kwargs.get('match')
if "submit" in self.request.POST:
url = reverse('GameView', args={q : 'match'})
else:
url = reverse('AddNotes', args={q : 'match'})
return url

What about get_absolute_url for the model object?
https://docs.djangoproject.com/en/1.10/ref/models/instances/#get-absolute-url
from django.urls import reverse
class GameModel(models.Model):
....
def get_absolute_url(self):
return reverse('game:single_page', args=[str(self.id)])
And in your GameView:
class GameView(generic.TemplateView):
template_name = "matchview.html"
def get_context_data(self, **kwargs):
....
def get_success_url(self, **kwargs):
return self.object.get_absolute_url()

Related

TypeError: context must be a dict rather than HttpResponseRedirect: Is it really impossible to redirect to a url without using test_func()?

I want to make it such that if the user has blocked me or if I have blocked the user, I want to redirect them back to the homepage and not allow them to view the detail page. Because this is a class based view, do you have any ways for me to achieve what I want and not affecting what already exists?
I tried to do all sorts of things but didn't work, and none of my friends could solve this. I cannot directly use return redirect(HomeFeed:main) because I have other contexts in the same view which I need it to return in the template.
I also do not want to use UserMixin's test_funct() which involves showing a 403 Forbidden Error because it isn’t user friendly and doesn’t show the user what exactly is happening. That’s why I want to do a redirect followed by django messages to inform them why they can’t view the page
class DetailBlogPostView(BlogPostMixin,DetailView):
template_name = 'HomeFeed/detail_blog.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
blog_post=self.get_object()
blog_post.save()
context['blog_post'] = blog_post
account = Account.objects.all()
context['account'] = account
if blog_post.interest_set.exists():
context["interest_pk"]=blog_post.interest_set.first().pk
if blog_post.author in self.request.user.blocked_users.all():
messages.warning(self.request, 'You cannot view post of ideas authors that you have blocked.', extra_tags='blockedposts')
hi = redirect('HomeFeed:main')
context['hi'] = hi
if blog_post.author in self.request.user.blocked_users.all():
messages.warning(self.request, 'You cannot view post of ideas authors that have blocked you.', extra_tags='blockeduposts')
hi = redirect('HomeFeed:main')
context['bye'] = bye
return context
You can do that in get method:
from django.views.generic import DetailView
from django.shortcuts import redirect
class MyDetailView(DetailView):
...
def get(self, request, *args, **kwargs):
self.object = blog_post = self.get_object()
blog_post.save()
if blog_post.author in request.user.blocked_users.all():
messages.warning(request, 'You cannot view post of ideas authors that you have blocked.', extra_tags='blockedposts')
return redirect('HomeFeed:main')
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
...
UPDATE
YOUR VIEW:
class DetailBlogPostView(BlogPostMixin,DetailView):
template_name = 'HomeFeed/detail_blog.html'
def get(self, request, *args, **kwargs):
self.object = blog_post = self.get_object()
blog_post.save()
if blog_post.author in request.user.blocked_users.all():
messages.warning(request, 'You cannot view post of ideas authors that you have blocked.', extra_tags='blockedposts')
return redirect('HomeFeed:main')
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['blog_post'] = self.object
context['account'] = Account.objects.all()
if self.object.interest_set.exists():
context["interest_pk"] = self.object.interest_set.first().pk
return context

I'm trying to incorporate multiple models into a single django view; how can I access the context **kwargs

I've tried this:
class MyClass(LoginRequiredMixin, UpdateView, ListView):
model = models.my_model
fields = ['first_model_field', 'second_model_field']
template_name = 'app/template_name.html'
extra_context = {'second_model': models.second_model.objects.get(pk=self.kwargs['pk']),#didn't work
'third_model':models.third_model.objects.get(pk='pk'),#didn't work
'fourth_model':models.fourth_model.objects.get(foreign_key_id = 'unique_kwarg')}#didn't work.
I also have url's that contain both the /<int:pk>/ kwarg and the /<int:unique_kwarg>/ kwarg.
I am having trouble figuring out how to reference the url **kwarg object.
def form_valid(self, form):
form_template_id = self.kwargs.get(self.pk_url_kwarg)
form.instance.model_id = model.objects.get(pk=form_template_id)
return super().form_valid(form)
and
def get_context_data(self, **kwargs):
"""Insert the form into the context dict."""
if 'unique_kwarg' not in kwargs:
kwargs['unique_kwarg'] = (self.kwargs.get(self.unique_kwarg))
kwargs['model_id'] = (model.objects.get(id=self.kwargs.get(self.unique_kwarg)))
return super().get_context_data(**kwargs)

What's a more efficient way to create a field-less form without a dummy forms.Form?

I'm trying to implement a form that simply presents data, and offers the user the choice of "Accept" or "Deny". I'm sending the data that I want to display by overriding the get_context_data() method, and I have two <input type="submit">'s on the template.
Here is the view:
class FriendResponseView(LoginRequiredMixin, FormView):
form_class = FriendResponseForm
template_name = 'user_profile/friend_response.html'
success_url = '/'
def get_context_data(self, **kwargs):
context = super(FriendResponseView, self).get_context_data(**kwargs)
context['respond_to_user'] = self.kwargs.get('username')
responding_profile = Profile.objects.get(
user__username=self.request.user)
requesting_profile = Profile.objects.get(
user__username=self.kwargs['username'])
friend_object = Friend.objects.get(requester=requesting_profile, accepter=responding_profile)
context['accepter_asks'] = friend_object.requester_asks
return context
def form_valid(self, form):
super(PairResponseView, self).form_valid(form)
if 'accept' in self.request.POST:
# do something
else:
return redirect('/')
Because the form does not accept any input or choices, I have this dummy form:
class FriendResponseForm(forms.Form):
pass
There must be a more efficient, Django way to achieve the same result. How would I go about it?
The best way is not to use a FormView at all, but a basic TemplateView. Then define post to do the submit logic.
class FriendResponseView(LoginRequiredMixin, TemplateView):
template_name = 'user_profile/friend_response.html'
def get_context_data(self, **kwargs):
...
def post(self, request):
if 'accept' in self.request.POST:
# do something
else:
return redirect('/')

Show a paginated ListView and an UpdateView on the same template page

I am trying to create a Django page where something can be updated and something can be viewed in a paginated table. The model looks like this:
class CostGroup(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse(
'costgroup_detail',
kwargs={
'costgroup_pk': self.pk,
}
)
class Cost(models.Model):
cost_group = models.ForeignKey(CostGroup)
amount = models.DecimalField(max_digits=50, decimal_places=2)
def get_absolute_url(self):
return reverse(
'cost_detail',
kwargs={
'cost_pk': self.pk,
}
)
So the edit form is for the name and description fields of the CostGroup model and the table should show a list of the 'amounts`
I previously had it working by just having an UpdateView for the form and the table included in the form template. Now though, as I want to include pagination on the table, I need to use two views on the same page. The page I have designed should look something like this in the end:
I am not worried about the styling at the moment my main focus at the moment is getting the form and the table on the same page. In its current state the only thing that I don't have is the pagination for the table:
The view currently looks like this:
class CostDetail(UpdateView):
model = models.Cost
pk_url_kwarg = 'cost_pk'
template_name = 'main/cost_detail.html'
form_class = forms.CostDetailEditForm
success_url = reverse_lazy('cost_list')
I have a feeling that leveraging the underlying mixins that the Django CBVs use is probably the way to go but I am not sure how to begin with this.
Any help would be much appreciated
Thanks for your time
(This clarification seemed to work better as a new answer)
It looks like you're dealing with both of the tables. The object level is using CostGroup, while the List view is showing the child records from Cost linked to a CostGroup. Assuming that is true, here's how I would proceed:
class CostDetail(ModelFormMixin, ListView):
model = CostGroup # Using the model of the record to be updated
form_class = YourFormName # If this isn't declared, get_form_class() will
# generate a model form
ordering = ['id']
paginate_by = 10
template_name = 'main/cost_detail.html' # Must be declared
def get_queryset(self):
# Set the queryset to use the Cost objects that match the selected CostGroup
self.queryset = Cost.objects.filter(cost_group = get_object())
# Use super to add the ordering needed for pagination
return super(CostDetail,self).get_queryset()
# We want to override get_object to avoid using the redefined get_queryset above
def get_object(self,queryset=None):
queryset = CostGroup.objects.all()
return super(CostDetail,self).get_object(queryset))
# Include the setting of self.object in get()
def get(self, request, *args, **kwargs):
# from BaseUpdateView
self.object = self.get_object()
return super(CostDetail,self).get(request, *args, **kwargs)
# Include the contexts from both
def get_context_data(self, **kwargs):
context = ModelFormMixin.get_context_data(**kwargs)
context = ListView.get_context_data(**context)
return context
# This is the post method found in the Update View
def post(self, request, *args, **kwargs):
# From BaseUpdateView
self.object = self.get_object()
# From ProcessFormView
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
I haven't tried to run this, so there may be errors. Good luck!
(Remember ccbv.co.uk is your friend when digging into Class-based Views)
An app I'm working on now uses a similar approach. I start with the ListView, bring in the FormMixin, and then bring in post() from the FormView.
class LinkListView(FormMixin, ListView):
model = Link
ordering = ['-created_on']
paginate_by = 10
template_name = 'links/link_list.html'
form_class = OtherUserInputForm
#=============================================================================#
#
# Handle form input
#
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
def get_success_url(self):
return reverse('links')
You may also wish to override get_object(), get_queryset(), and get_context().

Django - FormView combined with Mixin

I have to specify a success_url, otherwise I get an error. So how to specify it, in order to stay to the same page?
Also, is everything else correct regarding the SearchView, beucase I have a feeling that something is missing. My context should be composed by form, query, concepts, language and languages.
Thanks
urls.py
url(r'^(?P<langcode>[a-zA-Z-]+)/search/$', SearchView.as_view(), name='search').
views.py
class _LanguageMixin(object):
def dispatch(self, request, *args, **kwargs):
self.langcode = kwargs.pop("langcode")
self.language = get_object_or_404(Language, pk=self.langcode)
return super(_LanguageMixin, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(_LanguageMixin, self).get_context_data(**kwargs)
context.update({"language": self.language,
"languages": Language.objects.values_list('code',
flat=True)})
return context
class SearchView(_LanguageMixin, FormView):
template_name = "search.html"
form_class = SearchForm
success_url = #......
query = ''
concepts = []
def get_initial(self):
return {'langcode': self.langcode}
def get_context_data(self, **kwargs):
context = super(SearchView, self).get_context_data(**kwargs)
context.update({"query": self.query, "concepts": self.concepts})
return context
def form_valid(self, form):
self.query = form.cleaned_data['query']
self.concepts = # here is a long DB query; function(query)
return super(SearchView, self).form_valid(form)
[EDIT]
I did this:
def get_success_url(self):
return reverse('search', kwargs={'langcode': self.langcode})+"?query={}".format(self.query)
The form renders, but whenever I search for anything, I get back the empty search text field. And the URL looks something like this: http://localhost:8000/en-US/search/?query=asd
By default, a FormView (actually, any subclass of ProcessFormView) will return a HttpResponseRedirect in form_valid. As you are calling the super's method in your form_valid method, you also return a HttpResponseRedirect. In the process, the actual POST data is lost, and though you pass it as a GET parameter, it is not used in the actual form.
To fix this, you need to not call super in your form_valid method, but instead return a rendered template in a HttpResponse object, e.g.:
def form_valid(self, form):
self.query = form.cleaned_data['query']
self.concepts = # here is a long DB query; function(query)
return self.render_to_response(self.get_context_data(form=form))