DeleteView with a dynamic success_url dependent on id - django

I have an app for posts, with a url for each post:
url(r'^post/(?P<id>\w+)/$', 'single_post', name='single_post'),
On each post, I have comments. I would like to be able to delete each comment from the post page and return to the post that I was on.
I have the following url for deleting comments:
url(r'^comment/(?P<pk>\d+)/delete/$', CommentDelete.as_view(),
name='comment_delete'),
And I know from previous research that I need override the get_success_url, but I'm not sure how to reference the post id that I was just on. I think I need to use kwargs, but not sure how. I have this currently, but it doesn't work...
class CommentDelete(PermissionMixin, DeleteView):
model = Comment
def get_success_url(self):
return reverse_lazy( 'single_post',
kwargs = {'post.id': self.kwargs.get('post.id', None)},)
Ideas appreciated!

This should work:
def get_success_url(self):
# Assuming there is a ForeignKey from Comment to Post in your model
post = self.object.post
return reverse_lazy( 'single_post', kwargs={'post.id': post.id})
Django's DeleteView inherits from SingleObjectMixin, which contains the get_object method.

I had a similar problem when using a custom delete view. It was fixed by adding a class variable (static variable). An extract:
# Using FormView since I need to customize more than I can do with the standard DeleteView
class MyDeleteView(generic.FormView):
person_id = 0
def get_success_url(self):
# I cannot access the 'pk' of the deleted object here
return reverse('person_identity', kwargs={'person_id': self.person_id})
def form_valid(self, form):
plan = get_object_or_404(Plan, pk=self.kwargs['pk'])
self.person_id = plan.person_id
if form.cleaned_data.get('delete', False):
Plan.objects.filter(person=plan.person, date__gte=plan.date)\
.filter(date__gte=datetime.date.today())\
.delete()
return super(MyDeleteView, self).form_valid(form)

Related

Django: Display user's previous choices for a ModelForm in the template

I am trying to create a user profile page where users can see and update their preferences for certain things, like whether they are vegetarian, or have a particular allergy, etc. I want the data to be displayed as a form, with their current preferences already populating the form fields.
So I've created the following Model:
class FoodPreferences(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) # One user has one set of food prefs
vegetarian = models.BooleanField()
vegan = models.BooleanField()
...
that's referenced in my forms.py:
class FoodPreferencesForm(forms.ModelForm):
class Meta:
model = FoodPreferences
exclude = ('user', )
I've tried creating a view that inherits FormView and then referencing the form, like this:
class UserProfileView(generic.FormView):
template_name = "registration/profile.html"
form_class = FoodPreferencesForm
success_url = reverse_lazy('user_profile')
This saves the form to a instance of the model correctly, but obviously it just displays the blank form again, after updating, so the user has no idea what their current preferences are.
To implement this I thought I might need to override get() and post() to get the instance of FoodPreferences for the user, and then pass those values into the form like you would a request.POST object. However, firstly, I don't know how to do that, and secondly I'd be taking responsibility for correctly updating the database, which the FormView was already doing.
This is what I've got for that solution:
def get(self, request, *args, **kwargs):
prefs = FoodPreferences.objects.get(user=request.user)
form = self.form_class(prefs)
return render(request, self.template_name, {'form': form, })
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if not form.is_valid():
return render(request, self.template_name, {'form': form, 'error': 'Something went wrong.'})
curr_prefs = FoodPreferences.objects.update_or_create(form.fields)
prefs.save()
return render(request, self.template_name, {'form': form, })
but I get a TypeError: argument of type 'FoodPreferences' is not iterable on the line in get():
form = self.form_class(prefs)
because it's not expecting a model instance.
Am I thinking about this in the right way? This seems like a common enough problem that Django would have something inbuilt to do it, but I can't find anything.
You should only rarely need to define get or post in a class-based view, and you definitely don't here.
To start with, you need to use a more appropriate base class for your view. Here you want to update an existing item, so you should use UpdateView.
Secondly, you need to tell the class how to get the existing object to update, which you can do by definining get_object. So:
class UserProfileView(generic.UpdateView):
template_name = "registration/profile.html"
form_class = FoodPreferencesForm
success_url = reverse_lazy('user_profile')
def get_object(self, queryset=None):
return self.request.user.foodpreferences
# or, if you aren't certain that the object already exists:
obj, _ = FoodPreferences.objects.get_or_create(user=self.request.user)
return obj

Django Class Based View With ModelChoiceField

I've been working with Django for about 3 months now and feel I'm getting a bit better, working my way up to class based views. On the surface they seem cleaner and easier to understand and in some cases they are. In others, not so much. I am trying to use a simple drop down view via ModelChoiceField and a form. I can get it to work with a function based view as shown below in my views.py file:
def book_by_name(request):
form = BookByName(request.POST or None)
if request.method == 'POST':
if form.is_valid():
book_byname = form.cleaned_data['dropdown']
return HttpResponseRedirect(book_byname.get_absolute_url1())
return render(request,'library/book_list.html',{'form':form})
Here is my form in forms.py:
class BookByName(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Book.objects.none())
def __init__(self, *args, **kwargs):
super(BookByName, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].queryset = Book.objects.order_by('publisher')
This code works. When I have tried to convert to a Class Based View, that's when the trouble begins. I tried to do something like this in views.py:
class BookByNameView(FormView, View):
form_class = BookByName
initial = { 'Book' : Book }
template_name = 'library/book_list.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def get_success_url(self, *args):
return reverse_lazy('library:book_detail', args = (self.object.id,))
When using this with the same form, I receive an attribute error,
'BookByNameView' object has no attribute 'object'.
I've tried ListView as well and received several other errors along the way. The get_success_url also needs to take in a primary key and I can't figure out how to get that passed in as well. Again, I'm a 3 month Django newbie so please be gentle and thanks in advance for your thoughts and suggestions! I feel like I'm in the ballpark...just can't find my seat! I'm very open to doing this differently, if there's a cleaner/better way to do this!
Based on the latest feedback, it would appear the Class Based View should look like:
class BookNameView(FormView):
form_class = BookName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
Is this correct? I ran a test version of this and in response to your question as to why I am using self.object.id at all, I am trying to get the pk from the modelchoicefield that I am using to return the view I am trying to get. This may be where I am getting a bit lost. I am trying to get the detail view from the modelchoicefield dropdown, and return the book that is selected. However, I can't seem to pass the pk to this view successfully.
I updated my code to...
class BookByNameView(FormView, ListView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
But now it says error...Reverse for 'book_detail' with no arguments not found.
Why are you using self.object there at all? You used form.cleaned_data in the original view, that's what you should use in the class based version too. Note that the form is passed to form_valid.
Note that you've done lots of other weird things too. Your getmethod is pointless, as is your definition of the initial dict; you should delete them both. Also, FormView already inherits from View, there's no need to have View in your declaration explicitly.
You can override the form_valid() function in FormView to achieve what you want. If the form is valid then it is passed to the form_valid() function.
Try this:
class BookByNameView(FormView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def form_valid(self, form):
bookbyname = form.cleaned_data['dropdown']
return HttpResponseRedirect(bookbyname.get_absolute_url())

Django Generic UpdateView and Multitable Inheritance

Let's say I have the following models:
class Post(model):
...
class BlogPost(Post):
...
class OtherPost(Post):
...
Assume my url schema to edit a post is something like,
/site/post/\d+/edit
In other words, I don't have separate url paths for editing OtherPosts vs. BlogPost.
When using UpdateView, I need to set the model -- but of course, the actual model is a subclass of Post.
class Update(generics.UpdateView):
model = Post
What is the Djangoey/DRY way to handle this?
At the moment, looking over the UpdateView code, it looks like I could leave Update.model undefined, and override get_queryset, which would need to return a query with the right submodel. I would also need to override get_form to return the right form.
I'll post my solution when I get it working, but am looking for possibly better (DRYer) integrations.
It looks like the following method is working, which seems fairly minimal.
class Update(generic.edit.UpdateView):
model = Post
def get_form_class(self):
try:
if self.object.blogpost:
return BlogPostForm
except Post.DoesNotExist:
pass
try:
if self.object.otherpost:
return OtherPostForm
except Post.DoesNotExist:
pass
def get_object(self, queryset=None):
object = super(Update, self).get_object(queryset)
try:
return object.blogpost
except Post.DoesNotExist:
pass
try:
return object.otherpost
except Post.DoesNotExist:
pass
Or, if using a polymorphic mixin like InheritanceManager, then something like this:
class Update(generic.edit.UpdateView):
model = Post
form_class = {
BlogPost: BlogPostForm,
OtherPost: OtherPostForm,
}
def get_form_class(self):
return self.form_class[self.object.__class__]
def get_queryset(self):
return self.model.objects.select_subclasses()

Django Success_url

I built a FormView with two fields in it. I want to use the form to pass queries to a view.
class SectorForm(forms.Form):
date = forms.DateField()
days = forms.IntegerField(max_value=365, min_value=1)
Is there a way I can take my FormView and build the success_url from the valid form fields?
So overriding the get_success_url.
def get_success_url(self):
return reverse_lazy('queryable-view' date days)
I thought I could pull it out from form_valid but never had success.
get_success_url doesn't have access to the Form instance, but form_valid does. Try something like this:
from django.shortcuts import redirect
class YourFormView(FormView):
# ...
def form_valid(self, form):
date, days = form.cleaned_data['date'], form.cleaned_data['days']
return redirect('queryable-view', date, days)
I had similar problem once. When you work with forms, you may not use kwargs and get_success_url doesn't have access to the Form (as told before)
I have get around as:
class YourView(CreateView):
appointment_id =0
def form_valid(self,form):
appointment = form.save()
self.appointment_id = appointment.pk
print self.appointment_id
return super(AppointmentCreate,self).form_valid(form)
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('payment', kwargs = {'appid': self.appointment_id})

success_url in UpdateView, based on passed value

How can I set success_url based on a parameter?
I really want to go back to where I came from, not some static place. In pseudo code:
url(r'^entry/(?P<pk>\d+)/edit/(?P<category>\d+)',
UpdateView.as_view(model=Entry,
template_name='generic_form_popup.html',
success_url='/category/%(category)')),
Which would mean: edit entry pk and then return to 'category'. Here an entry can be part of multiple categories.
Create a class MyUpdateView inheritted from UpdateView and override get_success_url method:
class MyUpdateView(UpdateView):
def get_success_url(self):
pass #return the appropriate success url
Also i like to pass such parameters like template_name and model inside of inheritted class view, but not in .as_view() in urls.py
Had the same issue. Was able to get the paramater from self.kwargs as Dima mentioned:
def get_success_url(self):
if 'slug' in self.kwargs:
slug = self.kwargs['slug']
else:
slug = 'demo'
return reverse('app_upload', kwargs={'pk': self._id, 'slug': slug})
I found a way which is useful and very simple. Check it out.
class EmployerUpdateView(UpdateView):
model = Employer
#other stuff.... to be specified
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse("view-employer", kwargs={"pk": pk})
Define get_absolute_url(self) on your model. Example
class Poll(models.Model):
question = models.CharField(max_length=100)
slug = models.SlugField(max_length=50)
# etc ...
def get_absolute_url(self):
return reverse('poll', args=[self.slug])
If your PollUpdateView(UpdateView) loads an instance of that model as object, it will by default look for a get_absolute_url() method to figure out where to redirect to after the POST. Then
url(r'^polls/(?P<slug>\w+)/, UpdateView.as_view(
model=Poll, template_name='generic_form_popup.html'),
should do.
Why don't you add a 'next' parameter to your form (template) and catch it in your view. It's common practice to achieve redirecting this way.