Django: How to use request.POST in a delete view - python

I have a form sending data to a view via POST method. My problem is that I cant access the form's post data in the deleteview. After the song is deleted, I want the user to be sent back to the album-detail page to which the song belongs. My code is as shown below:
The form (inside album_detail.html):
<form action="{% url 'music:delete-song' pk=song.pk album_id=song.album_id %}" method="POST">
{% csrf_token %}
<input type="hidden" name="album_id" value="{{ song.album_id }}" />
<button type="submit" class="btn btn-danger btn-xs" role="button">Delete</button>
</form>
The urls.py:
#----
app_name = 'music'
urlpatterns = [
#----
url(r'^album/(?P<pk>[0-9]+)/detail/$', views.AlbumDetail.as_view(), name='album-detail'),
url(r'^song/(?P<pk>[0-9]+)/delete/(?P<album_id>[0-9]+)/$', views.SongDelete.as_view(), name='delete-song'),
]
And finally the view:
class SongDelete(DeleteView):
model = Song
album_id = request.POST.get('album_id')
success_url = reverse_lazy('music:album-detail', kwargs={'pk': album_id})
The album_id can not be set as above. How can I set the album_id to the post data album_id from the form, so that the user is sent back to the album-detail URL?

By implementing your views get_success_url of course:
def get_success_url(self):
album_id = self.request.POST.get('album_id') # Do something else if it's missing?
return reverse( # no need for lazy here
'music:album-detail',
kwargs={'pk': album_id}
)

You Have to override delete() and get_success_url()
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
self.album = self.object.album # assuming that song have a foreignkey reference to album model
self.object.delete()
success_url = self.get_success_url()
return HttpResponseRedirect(success_url)
def get_success_url(self):
album = self.album
return reverse(
'music:album-detail',
kwargs={'pk': album.id}
)
success_url is obtained from get_success_url() method and that method is called from delete(). Instead of calling reverse you can do something like
def get_success_url(self):
return `/album/details/' + str(self.album.id)
To make it simpler. Or you can directory provide redirect_url in delete method.
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
self.album = self.object.album # assuming that song have a foreignkey reference to album model
self.object.delete()
return HttpResponseRedirect('/album/details/' + str(self.album.id))
Refer to this link to get help on class based views

Related

Method Not Allowed (POST): /home/

I have a one page app with a form and a data table. The page load fine, but the problem is the Form is not working when I press the "SUBMIT" Button.
When I press the "SUBMIT" Button it give me this error Method Not Allowed (POST): /home/
Thanks you for the help guys!
views.py
def _get_form(request, formcls, prefix):
data = request.POST if prefix in request.POST else None
return formcls(data, prefix=prefix)
all_items = List.objects.all
class Myview(TemplateView):
template_name = 'data_list/home.html'
all_items = List.objects.all
def get(self, request, *args, **kwargs):
return self.render_to_response({'scrape': Scrape(prefix="scrape_pre"), 'all_items': all_items})
def scrape(self, request, *args, **kwargs):
scrape = _get_form(request, Scrape, 'scrape_pre')
if request.method == "POST":
scrape = _get_form(request, Scrape, 'scrape_pre')
if scrape.is_valid():
print("Worked")
return self.render_to_response({'scrape': scrape})
def home(self, request, *args, **kwargs):
all_items = List.objects.all
return render(request, "data_list/home.html", {"all_items": all_items})
forms.py
class Scrape(forms.ModelForm):
url = forms.CharField()
class Meta:
model = List
fields = ["item", "site"]
urls.py
from django.urls import path, include
from . import views
urlpatterns = [
path("", views.add, name="add"),
path("scrape/", views.scrape, name="scrape"),
path("home/", views.Myview.as_view(), name="home"),
path("delete/<list_id>", views.delete, name="delete"),
path("datacontent/<list_id>", views.datacontent, name="datacontent")
]
home.html
<div>
<form action="" method="post" >
{% csrf_token %}
{{ scrape|crispy }}
<pre></pre>
<button class="btn btn-outline-info" type="submit" value="Submit">SUBMIT</button>
<pre></pre><pre></pre><pre></pre><pre></pre>
</form>
</div>
<table class="table">
.....
You can't send a post request (method='post' in the form definition) if your backend doesn't implement the post function, which is responsible for responding the post requests. You should change your 'scrape' function to 'post'.

Django UpdateView: cannot get form fields to show database values

I found multiple answers to this same questions but unfortunately, I can't seem to figure it out :(
The form has a drop-down list for the 'subcategory' field in my model 'PhysicalPart', the values of the 'subcategory' field are updated dynamically upon the form creation (using a 'category' parameter).
Unfortunately, I can't get the drop-down to show all subcategories AND have the one from the database selected at the same time. I can't seem to retrieve the 'short_description' value either from the database.
It used to work before I learned about UpdateView class and decided to use it instead...
Any insight on how-to workaround my problem would be appreciated!
forms.py
class PartForm(forms.ModelForm):
subcategory = forms.ChoiceField(choices=[])
class Meta:
model = PhysicalPart
fields = ['subcategory', 'short_description']
views.py
class PartUpdate(UpdateView):
model = PhysicalPart
template_name = 'part_update.html'
form_class = PartForm
def post(self, request, *args, **kwargs):
# Load model instance
self.object = self.get_object()
# Load form
form = super(PartUpdate, self).get_form(self.form_class)
# Populating subcategory choices
form.fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Check if form valid and save data
if form.is_valid():
form.save()
return redirect('part-list')
# Update context before rendering
context = self.get_context_data(**kwargs)
context['part_id'] = self.object.pk
context['part_category'] = self.object.category
context['manufacturing_list'] = self.object.manufacturing.all()
return render(request, self.template_name, context)
html
<form action="{% url 'part-update' pk=part_id category=part_category %}" method="post" style="display: inline">
{% csrf_token %}
<div class="form">
<p class="font-weight-bold">Type</br>
{{ form.subcategory }}
</p>
</div>
<div class="form">
<p class="font-weight-bold">Short Description</br>
{{ form.short_description }}
</p>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
<form action="{% url 'part-list' %}" style="display: inline">
<button type="submit" class="btn btn-danger">Cancel</button>
</form>
My problem was that I did not differentiate the "GET" versus the "POST" calls in the UpdateView class, I was trying to do everything in the post() method. It took me a while to figure it out but now I think it's clear.
I originally used the get() method but I realize that get_context_data() was better suited as it automatically loads most of the context (eg. the instance and the form), instead of having to do everything from scratch in the get() method.
Scrubbing through the code of the UpdateView class here, it also seemed necessary to add ModelFormMixin into the declaration of the PartUpdate class so that the get_context_data() method automatically loads the form associated to the target model/instance (else it looks like it won't do it).
Here is my updated views.py code:
class PartUpdate(UpdateView, ModelFormMixin):
model = PhysicalPart
template_name = 'part_update.html'
form_class = PartForm
success_url = reverse_lazy('part-list')
def get_context_data(self, **kwargs):
# Load context from GET request
context = super(PartUpdate, self).get_context_data(**kwargs)
# Get id from PhysicalPart instance
context['part_id'] = self.object.id
# Get category from PhysicalPart instance
context['part_category'] = self.object.category
# Add choices to form 'subcategory' field
context['form'].fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Return context to be used in form view
return context
def post(self, request, *args, **kwargs):
# Get instance of PhysicalPart
self.object = self.get_object()
# Load form
form = self.get_form()
# Add choices to form 'subcategory' field
form.fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Check if form is valid and save PhysicalPart instance
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
From my understanding you are trying to edit an instance. This is how you do it in Django, it should autopopulate your inputs with the proper values :
my_record = MyModel.objects.get(id=XXX)
form = MyModelForm(instance=my_record)
More details on this answer : how to edit model data using django forms
If your models are properly done (with relations) you shouldn't need to provide the choices for the Select.

How to load a comment form in DetailView?

I was able to load the comments that were added through the admin page but I am not able to make a form in the DetailView itself
I have tried adding a form in the detailview template but I still don't see the form in the site
#views.py
class MessageDetailView(DetailView):
model = Message
template_name = "messaging/detail.html"
#queryset = Message.objects.all()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = Comment.objects.filter(message=self.object)
return context
#detail.html
<form method="POST">
{% csrf_token %}
<h3>Write a New Comment</h3>
<div class="messagebox">
{{ form|crispy }}
<button class="btn" type="submit">
Post Comment
</button>
</div>
</form>
#forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ("comment")
#models.py
class Comment(models.Model):
message = models.ForeignKey(Message,on_delete=models.CASCADE)
comment = models.TextField(max_length=50)
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return "Comment on {}".format(str(self.date_posted))
The comments loaded in the site, but the form didn't load, any way to solve this problem? Please provide some code in the answer instead of just linking me to a documentary.
you didn't pass the form to your template:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = Comment.objects.filter(message=self.object)
context['form'] = CommentForm()
return context

Django model CreateView does not render form fields in HTML

I am trying to setup a class based 'CreateView' for a model in my django site, but when the create html page renders, the model fields are not rendered. Only the submit button shows up on the web page. However, when debugging, I overrided the 'form_invalid' method in the class view, and the form object had the required HTML for all fields stored in the object. If I take this HTML and manually add it to the HTML of the create page in the browser I can fill out the fields and post the data to the database.
At this point I have not found an obvious answer as to why the form fields are not rendered so any help on this would be greatly appreciated.
environment used: python 3.7.3, django 2.2.3
Solution:
This issue was fixed by changing the form name in the view context data.
In views.py:
def get_context_data(self, *args, **kwargs):
context = super(CreateAlertView, self).get_context_data(**kwargs)
context["alert_form"]=context["form"]
return context
Or...
In the HTML template change 'alert_form' to 'form' to match the default context.
models.py:
class Alert(models.Model):
RAIN = 'Rain'
SNOW = 'Snow'
COLD = 'Cold'
HEAT = 'Heat'
WEATHER_CHOICES = [
(RAIN, 'Rain'),
(SNOW, 'Snow'),
(COLD, 'Cold'),
(HEAT, 'Heat'),
]
DAILY = 'Daily'
WEEKLY = 'Weekly'
INTERVAL_CHOICES = [
(DAILY, 'Daily'),
(WEEKLY, 'Weekly'),
]
weather_type = models.CharField(max_length=15, choices=WEATHER_CHOICES, default=RAIN)
interval = models.CharField(max_length=10, choices=INTERVAL_CHOICES, default=DAILY)
search_length = models.IntegerField(default=1)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
active = models.BooleanField(default=False)
views.py:
class CreateAlertView(LoginRequiredMixin, CreateView):
template_name = 'users/alert_form.html'
#model = Alert
form_class = AlertModelForm
success_url = 'users/profile/'
def form_valid(self, form):
print('validation')
form.instance.user = self.request.user
return super().form_valid(form)
def form_invalid(self, form):
print(form) # check form HTML here
return super().form_invalid(form)
forms.py:
class AlertModelForm(ModelForm):
class Meta:
model = Alert
exclude = ['user']
urls.py:
urlpatterns = [
path('alert/create/', CreateAlertView.as_view(), name='alert'),
]
html template:
<h1>create an alert</h1>
<form method="post">
{% csrf_token %}
{{ alert_form.as_p }}
{{ alert_form.non_field_errors }}
{{ field.errors }}
<button type="submit">Save changes</button>
</form>
Create page as rendered:
Create page with manually modified HTML:
The context name for the form set by the CreateView (FormMixin) is "form", your template is referencing "alert_form"
Here is a helpful website for seeing all options available in the class based views

Why the form return 'Method not allowed' while extending CreateView CBV in DJANGO

I am not sure what is not working correctly here. The CBV CreateView include a form but when I try to click on 'submit' in the template I receive the 'error' Method Not Allowed (POST)
forms.py
class DateInput(forms.DateInput):
input_type = 'date'
class BookingForm(forms.ModelForm):
class Meta:
model = Booking
fields = ('check_in',
'check_out')
widgets = {
'check_in': DateInput(),
'check_out': DateInput()
}
class PropertyDetailView(DetailView):
model = PropertyListing
context_object_name = 'name'
template_name = 'core/property-detail.html'
def get_context_data(self, *args, **kwargs):
context = super(PropertyDetailView, self).get_context_data(**kwargs)
context['property_details'] = PropertyListing.objects.filter(pk=self.kwargs.get('pk'))
# Form
context['form'] = BookingForm()
return context
just the form
HTML
<form class="col-sm-3" role="form" action="" method="POST">
{% csrf_token %}
{{ form|crispy }}
<input class="btn btn-primary" type="submit" value="Create" />
</form>
Does anybody have an idea why?
Like the error says, a DetailView [Django-doc] does not implement a handler for POST requests. Therefore if you make a POST request to the handler, it will produce a HTTP 405 error: Method not allowed.
We thus will need to implement a handler for a POST request ourselves. The good news is that a lot of functionality is already implemented in the ModelFormMixin [Django-doc]. We thus can implement this like:
class PropertyDetailView(ModelFormMixin, DetailView):
model = PropertyListing
context_object_name = 'name'
template_name = 'core/property-detail.html'
form_class = BookingForm
success_url = ...
def get_context_data(self, *args, **kwargs):
context = super(PropertyDetailView, self).get_context_data(**kwargs)
context['property_details'] = PropertyListing.objects.filter(pk=self.kwargs.get('pk'))
return context
def post(self, *args, **kwargs):
self.object = None
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
You thus do not need to add a Form to the context data (the ModelFormMixin will do that), nor do you need to handle the form yourself. You will however need to specify a success_url [Django-doc], or override the form_valid method [Django-doc].
That being said, it might be better to use a CreateView [Django-doc] or an UpdateView [Django-doc], and just apply some code changes to add details of your objects to it.

Categories

Resources