Django AJAX delete object in deleteView - django

Good day.
I'm trying to understand how to use ajax by deleting a object.
Main problem, i don't understand how to pass slug argument to url in AJAX call.
path('posts/<slug:slug>/delete/', DeletePost.as_view(), name='delete_post')
class DeletePost(CsrfExemptMixin, DeleteView):
queryset = Post.objects.all()
def get_object(self, queryset=None):
obj = super(DeletePost, self).get_object()
profile = Profile.objects.get(user_id__exact=self.request.user.id)
if obj.profile_id != profile.id:
raise Http404
return obj
def delete(self, request, *args, **kwargs):
self.get_object().delete()
payload = {'delete': 'ok'}
return JsonResponse(payload)
What i also want to understand, is the functionality correct? I've tested without json and get_object returns the correct object.
{% for post in user_posts %}
<tr id="post_table">
<th scope="row">
<input type="checkbox" aria-label="Checkbox" style="display: none">
</th>
<td class="tm-product-name">{{ post.title }}</td>
<td class="text-center">145</td>
<td class="text-center">{{ post.total_likes }}</td>
<td>{{ post.created_at }}</td>
<td><button class="delete-icon" onclick='deletePost({{ post.slug }})'>btn</button></td>
{#<td><i class="fas fa-trash-alt tm-trash-icon delete-icon" id="{{ post.id }}"></i></td>#}
<td>
{#<form method="post" action="{% url 'post:delete_post' post.slug %}" id="delete">#}
{#{% csrf_token %}#}
{#<i class="fas fa-trash-alt tm-trash-icon"></i>#}
{#</form>#}
</td>
</tr>
{% endfor %}
<script>
function deletePost(slug) {
let action = confirm('Are you sure you want to delete this post?');
if (action !== false) {
$.ajax({
method: 'POST',
url: '{% url 'post:delete_post'%}',
success: function (data) {
if (data.delete()) {
$('#post_table').remove()
}
}
});
}
}
</script>
Obviously this is the error that i currently have.
Reverse for 'delete_post' with no arguments not found. 1 pattern(s) tried: ['posts/(?P<slug>[-a-zA-Z0-9_]+)/delete/$']
Technically i just need a ping to the correct DeleteView url with a correct slug, is there a need to parse json in the DeleteView ?

Anyway found a solution.
Changed my view to
class DeletePost(CsrfExemptMixin, SingleObjectMixin, View):
model = Post
def post(self, *args, **kwargs):
self.object = self.get_object()
self.object.delete()
data = {'success': 'OK'}
return JsonResponse(data)
and modified the ajax request
var slug = document.getElementById('delete-icon').getAttribute('data-id');
function deletePost() {
let action = confirm('Are you sure you want to delete this post?');
let url = '/posts/' + slug + '/delete/';
if (action != false) {
$.ajax({
method: 'POST',
url: url,
dataType: 'json',
success: function (data) {
$('#post_table').remove()
}
});
}
}

Related

Why an ajax query is called twice

I try to update database using ajax query
I get row table id on click to send to view for updating data
but as my ajax is called twice (why?), second call reverse first call
<table>
<tbody>
{% for dcf in datacorrections %}
<tr>
<td data-toggle="tooltip" data-placement="top" title="">{{ dcf.ide }}</td>
<td data-toggle="tooltip" data-placement="top" title="" id="{{ dcf.ide }}">{{ dcf.deativated }}</td>
</tr>
{% endfor %}
</tbody>
</table>
$('body').on('click','td', function() {
var _id = $(this).attr('id');
$.ajax({
type: 'POST',
url: "{% url 'monitoring:deactivate_dcf' %}",
data: { "id" : _id },
dataType: 'html',
success: function (response) {
obj = JSON.parse(response);
},
});
});
#login_required
#csrf_exempt
def deactivate_dcf(request):
if request.is_ajax() and request.method == "POST":
datacorrection_id = request.POST.get("id")
if DataCorrection.objects.filter(ide = datacorrection_id).exists():
if DataCorrection.objects.get(ide = datacorrection_id).deativated == False:
DataCorrection.objects.filter(ide = datacorrection_id).update(deativated=True)
else:
DataCorrection.objects.filter(ide = datacorrection_id).update(deativated=False)
return JsonResponse({"response": "success",}, status=200)
else:
return JsonResponse({"response": "failed","exist":"datacorrection not exist"}, status=404)
return JsonResponse({"response":"not_ajax"}, status=200)
Add class "clickable-td" to the clickable td:
<td data-toggle="tooltip" class="clickable-td" data-placement="top" title="" id="{{ dcf.ide }}">{{ dcf.deativated }}</td>
add this instead, it will trigger the event on this td and it should stop the propagation.
$('.clickable-td').click(function(e) {
e.preventDefault();
e.stopImmediatePropagation();
var _id = $(this).attr('id');
console.log(_id);
$.ajax({
method: 'POST',
url: "{% url 'monitoring:deactivate_dcf' %}",
data: { "id" : _id },
dataType: 'json',
success: function (response) {
console.log("we went throught !");
console.log(response);
},
})
});
If you go in the console inspector and find the console log of the ID then it should be fine.

Django: Access context data in POST and GET

I am very new to Django and I cannot work out how to access context data within a POST request so I don't have to repeat myself.
I believe that POST runs before get_context_data, but again unsure of what exactly to do here.
The page displays some data using a default of 30 days.
Then on the page there is a form to override that value, which is passed back to the POST method to then re-render the page.
Example of page.
views.py
class ProducerDetailView3(generic.DetailView):
model = Producer
template_name = 'producer_detail3.html'
def get_queryset(self, **kwargs):
#-date_check means to descending order based on that column
return Producer.objects.filter(owner_name__exact=self.kwargs['pk'],metasnapshot_date=date(1980, 1, 1))
# Update context with more information
def get_context_data(self, **kwargs):
# Call the base implementation first to get the context
context = super().get_context_data(**kwargs)
# Cannot update context here as you are accessing existing producer object context data.
# Create any data and add it to the context
how_many_days = self.kwargs['days'] # get DAYS from URL. KWARGS are passed in from the get request
context['day_filter'] = reverse('results:producer_detail', args=[self.kwargs['pk'], '300'])
context['results'] = Results.objects.all().filter(owner_name__exact=self.kwargs['pk']).order_by('-date_check')
context['chains_json_count'] = Results.objects.filter(owner_name__exact=self.kwargs['pk'],chains_json=True,date_check__gte=datetime.now()-timedelta(days=how_many_days)).count()
return context
def post(self, request, **kwargs):
day_filter = int(request.POST.get('day_filter'))
producer = Producer.objects.get(owner_name__exact=kwargs['pk'],metasnapshot_date=date(1980, 1, 1))
# Using reverse we create a URL to set the day filter to 300 and create a link
results = Results.objects.all().filter(owner_name__exact=kwargs['pk']).order_by('-date_check')
chains_json_count = Results.objects.filter(owner_name__exact=kwargs['pk'],chains_json=True,date_check__gte=datetime.now()-timedelta(days=day_filter)).count()
context = {
'producer': producer,
'results': results,
'chains_json_count': chains_json_count,
'day_filter_url': day_filter
}
return render(request, 'producer_detail3.html', context)
producer_detail3.html
<h1>{{ producer.owner_name }}</h1>
<div class="row">
<div class="col-md-8">
<img src="{{ producer.logo_svg }}" alt="" width="100%">
</div>
<div class="col-md-4">
<h5>Producer:owner_name{{ producer.owner_name }}</h5>
<h5>Producer.pk: {{ producer.pk }}</h5>
<!--a href="{{ day_filter_url }}"
class="btn btn-primary">
Set day filter to 300
</a>
<h5>{{ producer.url }}</h5-->
<form method="post">
<p><label for="day_filter">Set day filter value</label>
{% csrf_token %}
<input type="text" name="day_filter" size="40" id="day_filter"/></p>
<input type="submit"/>
</form>
<br>
<h5>Results: {{ chains_json_count }}</h5>
<table>
<tr>
<th>HTTP</th>
<th>Hyperion</th>
<th>Date</th>
</tr>
{% for result in results %}
<tr>
<td>{{result.http_check}}</td>
<td>{{result.hyperion_v2}}</td>
<td>{{result.date_check}}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
UPDATE:
I tried accessing the context via the POST method using:
def post(self, request, **kwargs):
context = self.get_context_data(**kwargs)
However I get the following error:
Request Method: POST
Request URL: http://127.0.0.1:8000/producers/dapplica/30
Django Version: 3.2.12
Exception Type: AttributeError
Exception Value:
'ProducerDetailView3' object has no attribute 'object'
Exception Location: /Users/user/project/django-larn/lib/python3.7/site-packages/django/views/generic/detail.py, line 94, in get_context_data
there is no "hidden" call to get_context_data - you need to populate the context by calling the method from your post() method after defining self.object like
def post (....):
....
# define the singel object to display
self.object = self.get_object()
# define the context
context = self.get_context_data(**kwargs)
# render
return self.render_to_response(context)
if necessary add additional items to context in post()

How to get Django to redirect to the requested page with the LoginRequiredMixin after logging in?

Background:
I have an inventory application that scrapes through our VM and storage environments to index things for my team to have some quick references without having to log into vCenter/Storage systems directly to get info.
Since it's dealing with some potentially sensitive info, I put authentication on it by way of using the LoginRequiredMixin in my Views classes. The authentication part has been in for a while and it works without issues. The part where I get stuck on is I implemented the next parameter in the login form, and it shows up in the URL when it prompts to login. I have a different view class for each kind of thing I'm tracking.
Goal:
When someone clicks on a link that requires authentication (basically every page except the main page), it redirects them to the page they want after requiring them to login.
I've seen a few other questions, but the recommendations mentioned haven't worked in my case for whatever reason. I've tried various implements of the redirect_field_name but it still ends up trying to redirect to /accounts/profile if I don't have it overridden in the settings page. I don't have the login_url field set, but the login page redirect works as expected.
----Code Snippets----
LoginView - Using Django's LoginView and authentication form as a base, and just piecing them together to keep my search capability at the top of the page.
class LoginView(SearchMixin, auth_views.LoginView):
template_name = 'VMwareInventory/Registration/login.html'
form = AuthenticationForm
def get_context_data(self, **kwargs):
context = super(LoginView, self).get_context_data()
context['login_form'] = self.form
return context
Example view that requires logging in. The url ends up correctly: /accounts/login?next=/Clusters/
class StorageView(SearchMixin, LoginRequiredMixin, ListView):
model = StorageSystem
template_name = 'VMwareInventory/Storage_templates/cluster_list_page.html'
context_object_name = 'cluster_list'
queryset = StorageSystem.objects.all()
def get_context_data(self, *args, **kwargs):
context = super(StorageView, self).get_context_data()
clusters = StorageSystem.objects.all()
context['clusters'] = clusters
return context
def get(self, request, *args, **kwargs):
if self.request.is_ajax():
sorting_method = self.request.GET.get('sorting_method')
ascending = self.request.GET.get('ascending')
data = []
query = StorageSystem.objects.all()
results = None
if ascending == "true":
results = query.order_by(sorting_method)
elif ascending != "true":
results = query.order_by(sorting_method).reverse()
else:
query = None
data = serialize('json', None)
if query is not None:
for obj in results:
url_link = '' + obj.name + ''
json_data = {"name": url_link, "node_count": obj.node_count}
data.append(json_data)
return JsonResponse(data=data, safe=False)
else:
return render(self.request, self.template_name, context=self.get_context_data())
Login.html page:
<div class="login" style="vertical-align: middle; height: 100%; margin-top: 13%">
<table class="login" style="border: none; margin-top: auto; margin-bottom: auto">
<tr>
<td>
{% if form.errors %}
<p>Your username and/or password is incorrect. Please try again</p>
{% endif %}
{% if next %}
<p>You need to login first before you can do that</p>
{% else %}
<p>To see this page, please login with Username and Password</p>
{% endif %}
<form method="post" action="{% url 'VMwareInventory:login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ login_form.username.label_tag }}</td>
<td>{{ login_form.username }}</td>
</tr>
<tr>
<td>{{ login_form.password.label_tag }}</td>
<td>{{ login_form.password }}</td>
</tr>
</table>
<div style="padding-top: 5px; text-align: center">
<input type="submit" value="login"/>
<input type="hidden" name="next" value="{{ next }}"/>
</div>
</form>
</td>
</tr>
</table>
urls:
path('accounts/login/', views.LoginView.as_view(), name='login'),
path('accounts/logout/', views.LogoutView.as_view(), name='logout'),
path('accounts/password_change/', views.PasswordChangeView.as_view(), name='password_change'),
path('accounts/password_change/done/', views.PasswordChangeDoneView.as_view(), name='password_change_done')
path('Clusters/', views.StorageView.as_view(), name='storList'), # cluster url for view listed above.
OK, so after doing a bit of digging into some of Django code and seeing how the process works, I discovered that the next value wasn't getting pushed into the POST data. So I ended up having to capture the next value from request.GET.get('next') and I pushed it to a redirect variable in the html (I changed that from the next variable that I had initially).
All in all, there were only a few changes that I made in the code to get this working. I didn't have to re-create the entire view (which was helpful).
Hopefully this will help some others with this same issue get this up and running without having to completely rewrite a View, or override a bunch of methods.
Edit: I had to add a check to make sure that if there was no next value, then it wouldn't populate the redirect field.
LoginView:
class LoginView(SearchMixin, auth_views.LoginView):
template_name = 'VMwareInventory/Registration/login.html'
redirect_field_name = "redirect" # added
redirect_authenticated_user = True # added
form = AuthenticationForm
def get_context_data(self, **kwargs):
context = super(LoginView, self).get_context_data()
context['login_form'] = self.form
if self.request.GET.get('next'):
context['redirect'] = self.request.GET.get('next') # added
return context
login.html:
<div class="login" style="vertical-align: middle; height: 100%; margin-top: 13%">
<table class="login" style="border: none; margin-top: auto; margin-bottom: auto">
<tr>
<td>
{% if form.errors %}
<p>Your username and/or password is incorrect. Please try again</p>
{% endif %}
{% if next %}
<p>You need to login first before you can do that</p>
{% else %}
<p>To see this page, please login with Username and Password</p>
{% endif %}
<form method="post" action="{% url 'VMwareInventory:login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ login_form.username.label_tag }}</td>
<td>{{ login_form.username }}</td>
</tr>
<tr>
<td>{{ login_form.password.label_tag }}</td>
<td>{{ login_form.password }}</td>
</tr>
</table>
<div style="padding-top: 5px; text-align: center">
<input type="submit" value="login"/>
<input type="hidden" name="redirect" value="{{ redirect }}"/> # changed from before
</div>
</form>
</td>
</tr>
</table>

Django cant return a class based view user id

My goal is to update the view using ajax. when the user enters a value in those 3 fields and save those fields to the database with for this user.
I have a user model with 3 text field as follow
class Q3Sign(models.Model):
title = models.CharField(max_length =255,blank =True)
title2 = models.CharField(max_length =255, blank = True)
title3 = models.CharField(max_length =255,blank =True)
user = models.ForeignKey(User, on_delete=models.CASCADE )
class Meta:
db_table = "Q3sign"
and my view is as fellow, I am getting the following error when I try to populate the fields.
django.db.utils.IntegrityError: NOT NULL constraint failed: Q3sign.user_id
class Createcourse(generic.CreateView):
model = Q3Sign
fields = ['title','title2','title3']
template_name = 'Q3canA/create_course.html'
success_url = reverse_lazy('create_course')
def create_course(self, request):
members = Q3Sign.objects.all()
return render (request,'Q3canA/create_course.html' , {'members':members})
def insert(request):
member = Q3Sign(title=request.POST['title'], title2=request.POST['title2'],
title3=request.POST['title3'], user=request.POST['user.id'])
member.save()
return redirect('create_course')
and here is my html
<div class="container">
<h2>Courses</h2>
<form method=post>
{% csrf_token %}
{{ form.as_p}}
<input type="submit" value="Create course" />
</form>
</div>
<form method="post">
{% csrf_token %}
<div class="form-inline">
<label>Course 1</label>
<input type="text" id="title" name="title" class="form-control"/>
</div>
<br />
<div class="form-inline">
<label>Course 2</label>
<input type="text" id="title2" name="title2" class="form-control"/>
<label>Course 3</label>
<input type="text" id="title3" name="title3" class="form-control"/>
</div>
<br />
<div class="col-md-4"></div>
<div class="col-md-4 form-group">
<button type="button" class="btn btn-primary form-control" id="submit">Submit</button>
</div>
</form>
<hr style="border-top:1px solid #000; clear: both;" />
<table class"table table-bordered">
<thead class = "alert-warning">
<tr>
<th>Course 1</th>
<th>Course 2</th>
<th>Course 3</th>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td></td>
<td>{{member.user.id}}</td>
<td>{{member.title}}</td>
<td>{{member.title2}}</td>
<td>{{member.title3}}</td>
</tr>
{% endfor%}
</tbody>
</table>
{% endblock %}
Update now the fields disappeared :
Here is my URLs
urlpatterns = [
path('admin/', admin.site.urls),
path('',views.home, name='home'),
#Auth
path('signup', views.Signup.as_view(), name = 'signup'),
path('login', auth_view.LoginView.as_view(), name = 'login'),
path('logout', auth_view.LogoutView.as_view(), name = 'logout'),
#Q3Course
path('createcourse',views.Createcourse.as_view(), name = 'create_course'),
url(r'^create_course', views.Createcourse.createcourse, name='create_course'),
]
urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
thanks to #Fifon the code now working.
The only issue I need to understand is why the if statement is not working.
if the fields are empty or if it contains a test it still saves it in the database and not raising the alert in the if clause?
$(document).ready(function(){
$('#submit').on('click', function(){
$title = $('#title').val();
$title2 = $('#title2').val();
$title3 = $('#title3').val();
$user = $('#user').val();
if($title.toLowerCase().indexOf('test') == -1 || $title2 == "" || $title3 == "" || $user = ""){
alert("Please complete field");
}else{
$.ajax({
type: "POST",
url: "/create_course",
data:{
title: $title,
title2: $title2,
title3: $title3,
user: $user,
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val()
},
success: function(){
alert('Save Data');
$('#title').val('');
$('#title2').val('');
$('#title3').val('');
$('#user').val('');
window.location = "/create_course";
}
});
}
});
});
Firstly, there is no need for your insert() method. As you have a CreateView, you should use this, which will handle creating your objects and inserting them into the database. You should make use of the form this CreateView provides for you, rather than manually creating HTML for the form. This will make your life easier and ensure proper validation takes place. Something like this:
<form method="POST" id="create-course-form">
{% csrf_token %}
{{ form.as_p}}
<input type="submit" value="Create course" />
</form>
Your HTML is a little bit confusing right now – there is a form rendered using the {{ form.as_p }} template tag, and a manually rendered form. Take care here.
In your AJAX call, you should submit the form data, to the URL associated with your CreateView. I can't see your urls.py, but for example:
$.ajax({
type: "POST",
url: "{% url 'course-create' %}",
data: $('#create-course-form').serialize(),
...
You also need to make sure the form doesn't get submitted with an ordinary POST request (you want to use AJAX), by using preventDefault():
$('#create-course-form').submit(function(e){
e.preventDefault();
...
Putting it all together, back in your Createcourse view, you should define the form_valid(self) method, so that you can add in the user info. As you are using AJAX, you need to return a JsonResponse. E.g.:
def form_valid(self, form):
if self.request.is_ajax():
self.object = form.save(commit=False)
self.object.user = request.user
self.object.save()
return JsonResponse({'success':True})
else:
return super(CreateView, self).form_valid(form)
You also need to handle the situation in which your form is invalid as follows:
def form_invalid(self, form):
if self.request.is_ajax():
return JsonResponse(form.errors, status=400)
else:
return super(CreateView, self).form_invalid(form)
Finally, you will need to handle the response in your JavaScript. Success doesn't necessarily mean the form was valid and the course was created. If there are errors, you will need to display them to the user.
Update: You have two URLs both called 'create_view'. Remove the second one. Everything can be done from the one CreateView (Createcourse). In order to still have access to 'members', define get_context_data() as follows:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["members"] = Q3Sign.objects.all()
return context

Sub-Classed View with Form Not Returning Form Data

I have a situation that I cannot reconcile. I have a sub-class that has a form that is not returning data from a POST request. The super-class also has a form and this IS returning data from the same POST request. That is to say that the super-class is requesting user input and there are several sub-classes that extend the super-class and request other data. The super-class always returns the data from it's part of the form, but the subclasses do not.
The subclasses return data in the POST, but the data doesn't make it back to the Django view. This seems like a bug, (but perhaps it's a feature) or another likely scenario is that I'm missing something. However, I cannot find it. Help!
A solution might be to have no super-class and just have a very non-DRY program. Yuck.
I've put in a few print() to terminal commands to look at what is coming back (or not coming back) to verify this.
I have also used my browser's Developer Tools to see that the POST does, in fact, have the necessary data in it that should be returned to the view for processing. However, this data does not make it back to the view.
The sub-class view code looks like this:
class MainView(SidebarBaseView):
main_form_class = MainViewForm
template_name = 'myapp/my_view.html'
def get(self, request, *args, **kwargs):
context = super(MainView, self).get(request, *args, **kwargs)
context.update(self.get_context_data(*args, **kwargs))
main_form_initial = {}
for detail in context['my_data']: # my_data comes from get_context_data()
field_name = detail.datatype.name
main_form_initial[field_name] = detail.note
main_form = self.main_form_class(initial=main_form_initial)
context['main_form'] = main_form
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
context = super(MainView, self).post(request, *args, **kwargs)
context.update(self.get_context_data(*args, **kwargs))
main_form = self.main_form_class(request.POST)
print('\n','main_form',main_form,'\n') #to terminal
if main_form.is_valid():
main_form_data = main_form.cleaned_data
print('\n', 'cleaned data', main_form_data, '\n') #to terminal
for detail in context['my_data']:
field_name = detail.datatype.name
detail.note = main_form_data[field_name]
detail.save()
main_form_initial = {}
for detail in context['my_data']:
field_name = detail.datatype.name
main_form_initial[field_name] = detail.note
main_form = self.main_form_class(initial=main_form_initial)
context['main_form'] = main_form
return render(request, self.template_name, context)
def get_context_data(self, *args, **kwargs):
context = super(CompanyStatsView, self).get_context_data(**kwargs)
...
return context
My form make dynamic fields since I need to send it a variable number of fields eventually:
class MyMainViewForm(forms.Form):
fields = {}
field_names = ['one','two','three','many']
def __init__(self, field_names, *args, **kwargs):
super(MyMainViewForm, self).__init__(*args, **kwargs)
for name in field_names:
self.fields[name] = forms.CharField(label=name, max_length=256, required=False, widget=forms.TextInput(attrs={'size':'36'}))
My template looks like this:
{% extends 'my_sidebar.html' %}
{% block content_right %}
<div class="container-fluid">
<h2>Table</h2>
<div class="col-sm-4">
<div class="table-responsive">
<table class="table">
<tbody>
{% for detail in my_data %}
<tr height="50">
<td>{{ detail.datatype.full_name }}:</td>
<td class="text-right">{{ detail }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col-sm-4">
<div class="table-responsive">
<table class="table">
<tbody>
<form action="." method="post"> {% csrf_token %}
{% for note in main_form %}
<tr height="50">
<td>{{ note }}</td>
</tr>
{% endfor %}
<p><input type="submit" value="Save"></p>
</form>
</tbody>
</table>
</div>
</div>
<div class="col-sm-4">
</div>
</div>
{% endblock %}
Any ideas?
The pertinent part of urls.py is:
from django.conf.urls import url
from . import views
app_name = 'my_app'
urlpatterns = [
url(r'^(?P<slug>[A-Z]+)/data/$', views.MainView.as_view(), name='main-view',),
...
]