Django DeleteView without slug and via POST - python

Hello I want to delete an object in a model but I don't want to show the id in the slug, so I realized I can send the data via a hidden tag in a form, but I didn't manage to make it work
Template
<form action="{% url "delete_url" %}" method="post">
{% csrf_token %}
<input type="hidden" name="pk" value={{ model.pk }}>
<button type="submit">Delete</button>
</form>
Url. Check I don't want slug
path("delete_view", views.MyDeleteView.as_view(), name="delete_url")
View
class MyDeleteView(DeleteView):
model=ModelName
success_url = reverse_lazy("success_url")

First of all, your sample code has various problems which need to be fixed:
The input type should be hidden, not hiden.
Your class based view name should have proper capitalization: MyDeleteView, not delete_view
Now, for your actual problem: The DeleteView uses SingleObjectMixin to identify the object (check this out https://ccbv.co.uk/projects/Django/2.0/django.views.generic.edit/DeleteView/). Thus, you need to override the get_object method of SingleObjectMixin so as to use the POST data instead of the slug to retrieve the object. Probably something like this should work (warning there's no error handling):
class MyDeleteView(DeleteView):
model=ModelName
success_url = reverse_lazy("success_url")
def get_object(self, queryset=None):
pk = self.request.POST['pk']
return self.get_queryset().filter(pk=pk).get()
For a gentle introduction to CBV I recommend my CBV guide: https://spapas.github.io/2018/03/19/comprehensive-django-cbv-guide/

Thanks to Serafeim I can find the way to do it, I have to replace the get_object() method and copy some code from the original method. I get the code from the link:
https://ccbv.co.uk/projects/Django/2.0/django.views.generic.edit/DeleteView/
Finaly:
class MyDeleteView(DeleteView):
model=ModelName
success_url = reverse_lazy("success_url")
def get_object(self, queryset=None):
pk = self.request.POST['pk']
if queryset is None:
queryset = self.get_queryset()
if pk is not None:
queryset = queryset.filter(pk=pk)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404("No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
return obj

Related

How can I use instaceof in Django template?

Hello there I have a template that is used by two views in this template a have a toggle for deleting objects and because I have 2 types of objects that can be deleted by the same form I want to verify somehow the instance of the object in orderd to decide which url to use for deleting. How can I write this in django template properly?
<form method="POST" action="{% if object instanceof Income %}{% url 'delete-income' object.id %}{% elif object instanceof Spending %}{% url 'delete-spending' object.id %}{% endif %}">
the views extend this custom mixin to not repeat code:
class ObjectDeleteViewMixin(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = None
template_name = 'users/incomes_&_spendings.html'
success_url = None
def test_func(self):
self.model = self.get_object()
if self.request.user == self.model.user:
return True
return False
the views
class IncomeDeleteView(ObjectDeleteViewMixin):
model = Income
success_url = reverse_lazy('incomes')
class SpendingDeleteView(ObjectDeleteViewMixin):
model = Spending
success_url = reverse_lazy('spendings')
urls:
path('incomes/delete/<int:pk>/', IncomeDeleteView.as_view(), name='delete-income'),
path('spendings/delete/<int:pk>/', SpendingDeleteView.as_view(), name='delete-spending'),
Please don't. Django templates are deliberately restricted, not to allow function calls, subscripting, etc. to prevent people from writing business logic in the template. A template is normally focused on rendering logic. By implementing business logic in the template, you often make it templates harder to understand, update, etc.
Probably the most elegant way to solve this, is to define a method in both the Income and the Spending class that returns the path to the delete url:
from django.urls import reverse
class Income(models.Model):
# …
def remove_url(self):
return reverse('delete-income', kwargs={'pk': self.pk})
# …
class Spending(models.Model):
# …
def remove_url(self):
return reverse('delete-spending', kwargs={'pk': self.pk})
then you can render this with:
<form method="POST" action="{{ object.remove_url }}">

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.

TypeError: int() argument must be a string, a bytes-like object or a number, not 'DeferredAttribute'

I want to make a comments section in my post detail page. For that i was watching a tutorial for that on youtube. Here the tutorial uses function based view and i want to make it class based view.
Can anyone please help me convert this to class based view
in function based view
def post_detail(request, slug=None):
instance = get_object_or_404(Post, slug=None)
content_type = ContentType.objects.get_for_model(Post)
obj_id = Post.id
comments = Comment.objects.filter(content_type=content_type, object_id=obj_id)
context = {
"title": instance.title,
"instance": instance,
"comments": comments,
}
return render(request, "post_detail.html", context)
so far i tried this way to make it class based which i know is wrong.
class PostDetailView(LoginRequiredMixin,DetailView):
model = Post
template_name = 'posts/post_detail.html'
content_type = ContentType.objects.get_for_model(Post)
obj_id = Post.id
comments = Comment.objects.filter(content_type=content_type, object_id=obj_id)
But this gives me error something like this
return int(value) TypeError: int() argument must be a string, a bytes-like object or a number, not 'DeferredAttribute'
There are issues in your function view as well. Looking at your class based view, if you want to display comments in your detail view, then you don't want any of those thing, all you need this,
class PostDetailView(DetailView):
model = Post
template_name = 'posts/post_detail.html'
To show comments related to the post all you need to use your related name, in your comment model you should name something like this,
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='postcomments')
# ... (other code)
To show this in html, all you need to do this,
{% for comment in post.postcomments.all %}
{{comment.text}} #add according to your model
{% endfor %}
To create comment in the same page you need to add some extra things, Make sure you have comment form as well.
class PostDetailView(DetailView):
model = Post
template_name = 'post/post_detail.html'
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['commentform'] = CommentForm()
return context
def post(self, request, pk):
post = get_object_or_404(Post, pk=pk) #Assuming you have <int:pk> in url, if you have slug change pk to slug
form = CommentForm(request.POST) #Add your comment form
if form.is_valid():
obj = form.save(commit=False)
obj.post = post
obj.user = self.request.user
obj.save()
return redirect('detail', post.pk) # Correct it according your urlpattern name
In your same post detail html you can simply add your same html as you have used in other form templates,
<form method="POST" action="" >
{% csrf_token %}
<p> {{form}} </p>
<button type="submit"> Create </button>
</form>

django form submit not working

I have created for interface for users to filter the content from database by values.
Class in View
class FilterCommentsUIView(TemplateView,FormView):
template_name = 'allcomments.html'
form_class = CommentFilterForm
def get_context_data(self, **kwargs):
context = super(FilterCommentsUIView, self).get_context_data(**kwargs)
logger.debug("Entered context data")
logger.debug(self.request.method)
if self.request.method == 'POST':
form = CommentFilterForm(self.request.POST)
loggeer.debug("Entered POST")
if form.is_valid():
logger.debug("Entered is valid")
parenttype = self.request.POST['pchoice']
users = self.request.POST.get('users')
tags = self.request.POST.get('tags')
fdate = self.request.POST.get('fdate')
tdate = self.request.POST.get('tdate')
typeuser = self.request.POST.get('typeuser')
query = self.request.POST.get('query')
userid = User.objects.get(username ='pavan')
comments = Item.objects.all().filter(user = userid.id).order_by('-created')
paginator = Paginator(comments,20)
page = self.request.GET.get('page')
try:
comments = paginator.page(page)
except PageNotAnInteger:
comments = paginator.page(1)
except EmptyPage:
comments = paginator.page(paginator.num_pages)
context['comments']=comments
context['form']=CommentFilterForm
else:
logger.debug("Entered isvalid else")
logger.debug(form.errors)
else:
logger.debug("Entered Else of POST")
form = CommentFilterForm()
comments = Item.objects.all().order_by('-created')
paginator = Paginator(comments, 20)
page = self.request.GET.get('page')
try:
comments = paginator.page(page)
except PageNotAnInteger:
comemnts = paginator.page(1)
except EmptyPage:
comments = paginator.page(paginator.num_pages)
context['comments']=comments
context['form']= form
return context
When i click on submit button it does not enter the POST if section. It always goes to else part. What am i doing wrong ?
Thanks in advance for help.
Pavan
As others have said you have issues with the fact all your code is in get_context_data. In all actuality you don't even need to use get_context_data to accomplish what you are trying to do.
From what I can see you are trying to get comments on the page. Then submit a form back to the page and get a filtered out set of comments returned, and rendered. Here is a view solution that better does what you want except using generic class based views more fully.
class FilterCommentsUIView(FormMixin, ListView):
template_name = 'allcomments.html'
form_class = CommentFilterForm
paginate_by = 20
model = Item
context_object_name = 'comments'
qs_kwargs = {}
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_queryset(self):
qs = super(FilterCommentsUIView, self).get_queryset()
if self.qs_kwargs:
return qs.filter(**self.qs_kwargs)
return qs
def form_valid(self, form):
parenttype = form.cleaned_data['pchoice']
users = form.cleaned_data('users')
tags = form.cleaned_data('tags')
fdate = form.cleaned_data('fdate')
tdate = form.cleaned_data('tdate')
typeuser = form.cleaned_data('typeuser')
query = form.cleaned_data('query')
userid = User.objects.get(username ='pavan')
# put logic here on what to expand into a filter on orm
self.qs_kwargs['user'] = userid.id
self.render_to_response(self.get_context_data())
I haven't run this code, but this is how it should work.
This uses the FormMixin to give us form functionality along with the ListView with your Item class. This takes care of querying for your objects and paginating them, along with a basic get method.
Also the ListView inherits from TemplateResponseMixin which has your template code so you don't need to inherit from TemplateView so in the code above I removed that.
Based on the very nature of the base View class which ListView uses the dispatch method detects whether you are doing a GET, POST, PUT, DELETE, etc... As such it calls the appropriate method on the view. In this case since you are doing a POST it will call the post method in the view. The FormView's implementation of post is this:
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Since we aren't using FormView, but are using FormMixin We need to include it.
So all it does is get the form class you set in self.form_class instantiates it, then checks if it is valid. If so calls the form_valid method. Thus we overrode the form_valid method above. Since it passes in the form with it and it has cleaned all the data we can use the normal django form of form.cleaned_data. This helps us with security so we should use that to get back information.
In the last part of the form_valid method we are returning render_to_response. Normally this just redirects to self.success_url, but we want to render the page so we just do a render_to_response and pass it in our context data. We call self.get_context_data() because that is what builds all the data for paginating our comments that we need.
Now comes a bit of the magic. if you notice there is self.qs_kwargs. This is something that is not normally in GCBV's, but something I added for this implementation. Here is where you would put logic to build a dictionary of filters to run in your orm call in get_queryset. We pack it into the dictionary, because in the get_queryset method above we unpack it all, if needed, to do our filtering.
So if you have:
qs_kwargs = {'user': user.id, 'created__gt': somedate}
And you do:
qs.filter(**qs_kwargs) or Item.objects.filter(**qs_kwargs)
That is roughly the same as doing:
qs.filter(user=user.id, created__gt=somedate) or Item.objects.filter(user=user.id, created__gt=somedate)
Finally, if you pay attention to the get_queryset method you notice it returns back all results, like you want, unless qs_kwargs is populated which happens from the form_valid method. So it takes in to account both GET and POST on the page.
Also to keep things simple your html should look something like this:
<html>
<head></head>
<body>
<div>
{% for comment in comments %}
{{ comment }}
{% endfor %}
<ul class="pagination">
{% if page_obj.has_previous() %}
<li>Previous</li>
{% endif %}
{% for pg in paginator.page_range %}
{% if page_obj.number == pg %}
<li class="active">{{ pg }}</li>
{% else %}
<li>{{ pg }}</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next() %}
<li>Next</li>
{% endif %}
</ul>
</div>
<div>
<form action="" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="submit" />
</form>
</div>
</body>
</html>
Hope that helps.
This needs to be done in the form_valid() method, not the get_context_data method. get_context_data is just used to provide extra data (aka context) to the template during the request.

Django's get_context_data function not working?

I'm practicing with Django's Class Based View.
It seems like my overridden get_context_data function is not working properly, but I have no idea what is wrong :(
My code:
urls.py
url(r'^user/(?P<pk>\d+)/posts/$', UserPosts.as_view(), name='user_post'),
views.py
class UserPosts(ListView):
template_name = 'app_blog/user_posts_page.html'
context_object_name = 'post_list'
def get_queryset(self):
self.user = get_object_or_404(User, id=self.kwargs['pk'])
return self.user.post_set.order_by('-id')
def get_context_data(self, **kwargs):
context = super(UserPosts, self).get_context_data(**kwargs)
context['user'] = self.user
return context
user_post_page.html
{% block content %}
<div class="main_content">
<h2>Welcome to {{user.username}}'s User Post Page!</he>
<ul>
{% for post in post_list %}
<li>{{post.post_title}}</li>
{% endfor %}
</ul>
</div>
{% endblock %}
The html page correctly displays the user's post_list, BUT the h2 tag displays:
Welcome to 's User Post Page!
I'm pretty sure I passed the 'user' variable in the get_context_data function, but the html page does not displa the user.username... Any idea why this is happening :(??
Thanks
Use another name that is not user. It seems like RequestContext overwrite user variable.
Please see the default TEMPLATE_CONTEXT_PROCESSORS, which set the django.contrib.auth.context_processors.auth.
If TEMPLATE_CONTEXT_PROCESSORS contains this processor, every RequestContext will contain these variables:
user – An auth.User instance representing the currently logged-in user (or an AnonymousUser instance, if the client isn’t logged in).
perms – An instance of django.contrib.auth.context_processors.PermWrapper, representing the permissions that the currently logged-in user has.
So you'd better give your user variable another name.
As the other answers said, don't use the variable name user. But even more importantly, in this particular case the ListView is not the best generic class to use; instead, you're better off using the DetailView, which makes your view much simpler:
class UserPosts(DetailView):
model = User
template_name = 'app_blog/user_posts_page.html'
context_object_name = 'listed_user'
The only change in your template is to use listed_user instead of the user variable. Since DetailView already looks into the pk URL argument, it will select and return the right User automatically.

Categories

Resources