POST vs GET method in Django's view function - python

My app's urls.py is:
from django.urls import path
from . import views
app_name = 'javascript'
urlpatterns = [
path('create_table', views.create_table, name='create_table')
My views.py is:
def create_table(request):
if request.method == 'POST':
row_data = "this is row data"
context = {'row_data': row_data}
return render(request, 'javascript/create_table.html',context)
My create_table.html is:
{% load static %}
<form action="" method="post">
{% csrf_token %}
<button id="create_table">Get data</button>
</form>
<div id="place_for_table"></div>
<script type="text/javascript">
var row_data = "{{ row_data }}"
</script>
<script src="{% static 'javascript/scripts/create_table.js' %}"></script>
And my create_table.js is:
function create_table() {
document.getElementById("place_for_table").innerHTML = row_data;
}
document.getElementById("create_table").onclick = function() {
create_table()
}
What I want to achieve is: when the /create table URL is requested, only the button is displayed. When the button is pressed, row_data variable's value is displayed below the button.
At this moment the data is displayed for a blink of an eye and then disappears.
I guess I am not distinguishing between POST and GET methods correctly in the view function. Also I have based my logic on the assumption that URL is requested using GET method by default. However if I put a print(request.method) at the beginning of my view, it prints POST.
Also, whenever I load the page for the first time or refresh it, I get a Firefox warning:"To display this page, Firefox must send information that will repeat any action (such as a search or order confirmation) that was performed earlier."
Other things I tried was to use class-based view with post and get defs or put the create_table's URL in button formaction= tag, but that doesn't help.
I would be thankful for any suggestions on how to achieve this.

The problem might be because when you are submitting the form it refreshes/reloads your page i.e. /create_table url because of which row_data resets
This Might help you to understand why the button submits even though you didn't explicitly provided type="submit" to the button inside your form.
Our aim will be to prevent the event to occur. It can be done using a simple return false statement in our onclick listener as given below:
function create_table() {
document.getElementById("place_for_table").innerHTML = row_data;
}
document.getElementById("create_table").onclick = function(event) {
create_table();
return false;
}

Related

How to create a "save and add another" button and show on Wagtail admin model page

Was wondering if it is possible to add a custom button within a Wagtail model page that will allow me to save (create) the current data fields and move on to another page. The "save and add another" button was already available in django and I want to have something like that on the Wagtail model page.
Thanks.
Wagtail has a few ways to customise content that is shown in the various Wagtail modeladmin views.
Code Example
Step 1 - create a custom CreateView
This will override the get_context_data method to provide the create_url, we could do this simpler (the context already has access to the instance) but it is nice to be explicit.
Override the get_success_url method to allow for a URL param of next to be set, if that exists the next URL will be this instead of the default behaviour.
products/wagtail_hooks.py
from wagtail.contrib.modeladmin.views import CreateView
# ... other imports
class CustomCreateView(CreateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['create_url'] = self.create_url
return context
def get_success_url(self):
next = self.request.GET.get('next')
if next:
return next
return super().get_success_url()
class ProductsModelAdmin(ModelAdmin):
create_view_class = CustomCreateView
Step 2 - Override the template to add a new button
Wagtail modeladmin uses a template path override approach, you will need to create a new template that aligns with your desired override (for example we may just want to override the product model create view only.
templates/modeladmin/products/products/create.html -> this says to override the create template for the products model within the products app.
Add the code below and check it is working, sometimes the templates path can be a bit tricky so ensure you can see the new button AND that the button has the data-next attribute that should be your create URL.
templates/modeladmin/products/products/create.html
{% extends "modeladmin/create.html" %}
{% load i18n wagtailadmin_tags %}
{% block form_actions %}
{{ block.super }}
<div class="dropdown dropup dropdown-button match-width">
<button type="submit" class="button button-secondary action-save button-longrunning" data-next="{{ create_url }}" data-clicked-text="{% trans 'Saving…' %}">
{% icon name="spinner" %}<em>{% trans 'Save & add another' %}</em>
</button>
</div>
{% endblock %}
Step 3 - Add JS to modify the form action URL on click
The next step requires a bit of JavaScript, we want to attach a different behaviour to the 'save and add another' button.
The JS used here does not require jQuery and should work in IE11
Once the DOM is loaded (which means JS can do things), find all the buttons with the data-next attribute.
Add a listener to the 'click' of each of those buttons which will dynamically update the form action attribute with the extra URL part.
This extra URL part gets read by the get_success_url method in the custom create class but only on a successful save, otherwise you will stay on the page and be able to fix errors.
{% block extra_js %}
{{ block.super}}
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('[data-next]').forEach(function(button) {
button.addEventListener('click', function(event) {
var form = document.querySelector('.content-wrapper form');
form.action = form.action + '?next=' + button.dataset.next;
});
});
})
</script>
{% endblock %}

Django/django-tables2 html table on row click to edit form

sorry this post may be messy not sure how do explain what I am looking for very well but here goes nothing.
I have a Django App and using django-table2 to print a data model to a table, the next thing I am looking to do it when the user clicks on the table row to redirect the page to a equivalent edit form
urls.py
path('', CustomerView.as_view(), name='customer'),
path('customer_edit/', views.customer_edit, name='customer_edit'),
tables.py
import django_tables2 as tables
from customer.models import Customer
class CustomerTable(tables.Table):
account = tables.Column(attrs={'td': {'class': 'account'}})
class Meta:
model = Customer
attrs = {'id': 'table'}
exclude = ('is_deleted',)
template_name = 'django_tables2/bootstrap-responsive.html'
views.py
from django.shortcuts import render
from django_tables2 import RequestConfig
from customer.models import Customer
from customer.tables import CustomerTable
from django.views.generic import TemplateView
class CustomerView(TemplateView):
template_name = 'customer/customer.html'
def get(self, request, *args, **kwargs):
table = CustomerTable(Customer.objects.all().filter(is_deleted=False))
RequestConfig(request).configure(table)
return render(request, 'customer/customer.html', {'table': table})
def customer_edit(request):
return render(request, 'customer/customer_edit.html')
template
{% extends 'base.html' %}
{% load render_table from django_tables2 %}
{% block head %}
<title>Dev Genie - Customers</title>
{% endblock %}
{% block body %}
<div class="input-group col-md-6">
<input type="button" class="btn btn-success" value="Add">
<input type="button" class="btn btn-danger" value="Delete">
<input class="form-control py-2" type="search" value="search" id="example-search-input">
<span class="input-group-append">
<button class="btn btn-outline-secondary" type="button">
<span class="glyphicon glyphicon-search"></span>
</button>
</span>
</div>
{% render_table table %}
<script>
$(document).ready(function () {
$('table:first').children('tbody:first').children('tr:first').css('background-color', '#0099ff');
$('table tbody tr').bind("mouseover", function () {
var colour = $(this).css("background-color");
$(this).css("background", '#0099ff');
$(this).bind("mouseout", function () {
$(this).css("background", colour);
});
});
$('table tbody tr').click(function () {
let account = $(this).closest('tr').find('td.account').text();
alert(account);
//on table row click event, pass back to django
});
});
</script>
{% endblock %}
I am struggling to get the account code from the onclick even to pass the account code back to Django to move to the next page to begin editing the record
I really think I am barking up the wrong tree with this
any help would be very much appreciated
I couldn't find any solution that suits my needs.
All the solutions I found requires some weird processing in Javascript and parsing slugs and PK's from the table to redirect to the correct URL.
My solution?
Define an absolute URL in your models.py
def get_absolute_url(self):
return reverse('product:detail', kwargs={'slug': self.slug})
Then in your tables.py, we add a data-href attribute to each column that we want to be clickable. This allows us to restrict which columns become clickable.
class ProductTable(tables.Table):
clickable = {'td': {'data-href': lambda record: record.get_absolute_url}}
name = tables.Column(attrs=clickable)
in_stock = tables.Column(attrs=clickable)
class Meta:
model = Product
fields = (name, in_stock)
And in your template just add this simple event listener,
$(document).ready(function($) {
$("td").click(function() {
window.location = $(this).data("href");
});
});
Alternatively, if you just want the whole row to be clickable, just use Row attributes as defined in the docs,
class ProductTable(tables.Table):
class Meta:
model = Product
row_attrs = {'data-href': lambda record: record.get_absolute_url}
fields = (name, in_stock)
and then change your template script too
$(document).ready(function($) {
$("tr").click(function() {
window.location = $(this).data("href");
});
});
I think i may have found a implementation for the above.
Putting a click event for a dialogue box with Django Tables2
it is for deleting a row but the concept is the same
I will test and check
Simple code to do that on row click or col
row_attrs = {
"onClick": lambda record: "document.location.href='/app/view/{0}';".format(record.id)
}
if you want to use it on col use tables.Column
Docs
Ok after spending this evening on this, I have found a way to perform this action without adding the href tag into the python code,
by using Ajax I can get the account code from the table and then pass this through to the url
$('table tbody tr').click(function () {
let account = $(this).closest('tr').find('td.account').text();
window.location = account;
});
adding the primary key to the url.py
path('<slug:account>/', views.customer_edit, name='customer_edit'),
and adding the customer_edit def to the views.py
def customer_edit(request, account):
customer = get_object_or_404(Customer, pk=account)
if request.method == 'POST':
form = CustomerEdit(request.POST, instance=customer)
if form.is_valid():
customer.save()
return redirect(reverse('customer:customer'))
else:
form = CustomerEdit(instance=customer)
args = {'customer': customer, 'form': form}
return render(request, 'customer/customer_edit.html', args)
this is the most optimum way I could find to redirect to another view from Django without having the url specified inside of the python file, I am 100% that there is better ways to do this but for now this will be the accepted answer
I may be a little confused about what you are trying to do. It seems like you are for some reason trying to have the view render a new response back from the click events on the table. That is why you are getting tripped up with all this JavaScript rendering. You should simply have those cells render as links that go to where you need them to.
Take a look at the django-tables2 documentation for TemplateColumn. You will want to just have it point to a template that creates the url given the record pk.
https://django-tables2.readthedocs.io/en/latest/pages/api-reference.html?highlight=templatecolumn#templatecolumn
tables.py
class CustomerTable(tables.Table):
account = tables.TemplateColumn(template_name="_account.html")
def render_title(self, record, value, column, bound_column, bound_row):
value = self.value_title(record, value)
return mark_safe( # noqa: S308, S703
column.render(record, self, value, bound_column, bound_row=bound_row)
)
_account.html
<a href={% url('customer_edit', args=[record.pk]) %}>your text here</a>

How to add alert popup boxes in django?

I've been going through the django documentation, but so far everything I've tried hasn't worked for me. I'm trying to make it so if the user enters the url to a certain page and they are not already logged in then they are redirected to the login page that gives them a popup error message that says they don't have access to the page until they have logged in.
Here is a small snippet of code from the views.py
def home(request):
if request.session.test_cookie_worked():
return render(request, 'page/home.html')
else:
return render(request, 'page/login.html')
Everything works except for the error message not popping up if they are redirected to the login page. I've looked at other stack overflow questions that people have asked that are similar to this, but those don't seem to work for me either. Any advice?
Here's how I'm doing it.
First, if the view code determines that the popup should be displayed, call render() with a named flag in the context:
return render(request, 'page/home.html', {'some_flag': True})
Then, in your page template, if the flag is set, display a <div> element:
{% if some_flag %}
<div id="some_flag" title="Some Flag">
<p>Some Text Here</p>
</div>
{% endif %}
Also in the page template, in the <head> section, create a JavaScript function to display the <div> as a popup dialog:
<head>
<script type="text/javascript">
$(document).ready(function() {
$(function() {
$( "#some_flag" ).dialog({
modal: true,
closeOnEscape: false,
dialogClass: "no-close",
resizable: false,
draggable: false,
width: 600,
buttons: [
{
text: "OK",
click: function() {
$( this ).dialog( "close" );
}
}
]
});
});
});
</script>
</head>
This solution also requires that you use the jQuery library.
I'm sure there are drawbacks to doing it this way, but it works for me.
We can pass a variable in context object like a flag.
return render(request, 'index.html', {'alert_flag': True})
and then in templates simply do a check for the alert_flag.
{% if alert_flag %}
<script>alert("Some Message.")</script>
{% endif %}
Here no jQuery code is required and I am using native alert.

Django: provide global method to all (class-based) views?

I need a button, included in the header of every page in my Django application, to trigger some global method, to toggle a "mode" setting in the session. There are two modes: 'preview' and 'publish'.
One solution I've come up with: duplicate a post() method in every (class-based) view, to handle the mode change. This hardly seems DRY.
Another would be to inherit all of my CBVs from a single superclass, or use a mixin. I suppose this is a possibility.
A better solution perhaps: I've setup a context_processor to handle publishing the mode globally to my templates. This works fine. I've also setup a middleware class with process_request which could, theoretically, handle POST requests globally. But how do I call this process_request method from my templates?
My current stab at it follows. How do I toggle the "preview" and "publish" buttons in my template, and call the middleware?
template.html:
<html>
<head></head>
<body>
<header>
<form method="post">
{% csrf_token %}
<!-- button toggle -->
{% if mode == 'preview' %}
<button name="mode" value="publish">Publish</button>
{% else %}
<button name="mode" value="preview">Preview</button>
{% endif %}
</form>
</header>
</body>
</html>
middleware.py:
class MyMiddleware(object):
def process_request(self, request):
update_mode(request)
def update_mode(request, new_mode=None): # how do I call this from template?
modes = [
'preview',
'publish'
]
# ensure default
if not request.session.get('mode', None):
request.session['mode'] = 'preview'
# set new mode
if new_mode and new_mode in modes:
request.session['mode'] = new_mode
context_processor.py:
def template_mode(request):
context = {
'mode': request.session['mode']
}
return context
You don't "call" middleware: that's not at all how it works. Middleware is invoked on every request, so in your case the update_mode function would always run.
A better solution would be to get the form containing the button to post to a new URL, which invokes a view to update the mode. You could add a hidden field containing the current URL - which you can get from request.path - and the update mode view can redirect back to that URL after doing its work.
I wouldn't do it that way - how about making a template tag for the form?
In your templatetags.py:
def set_session_mode_form():
return {'session_form': SessionForm()}
register.inclusion)tag("<path-to-your-template>",set_session_mode_form)
Then your session form sends to a view that updates the session variable you want.
To use it, just load the tags on your page and use {% include %}. This way, its very easy to add to any page, and keeps it DRY.

django and dropzone how to post form

I have gone through django and dropzone. I have implemented drag and drop feature of dropzone in django.
My template look like:
<form id="add-multiple" class="dropzone" action="{% url "name_add" %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
</form>
<button id="submit-all" type="submit" value="Submit" form="add-multiple">
Submit all files
</button>
<script src="{% static 'js/dropzone.js' %}"></script>
<script type="text/javascript">
Dropzone.options.myDropzone = {
// Prevents Dropzone from uploading dropped files immediately
autoProcessQueue : false,
paramName: "files",
init : function() {
var submitButton = document.querySelector("#submit-all")
myDropzone = this;
submitButton.addEventListener("click", function() {
myDropzone.processQueue();
// Tell Dropzone to process all queued files.
});
// You might want to show the submit button only when
// files are dropped here:
this.on("addedfile", function() {
// Show submit button here and/or inform user to click it.
});
}
};
</script>
Here, I am getting the css and uploading perfectly, but when I submit the form, it doesn't call the {% url "name_add" %} url. I mean, the form is not posted and it doesn't call that url.
I followed this tutorial https://amatellanes.wordpress.com/2013/11/05/dropzonejs-django-how-to-build-a-file-upload-form/ to achieve this.
First thing, my form is not being posted or it says it is not calling the url in form action. Second there is not any use of my form that I have created in forms.py to upload a file. In the tutorial also there is no use of form. Why is that and without form how form can be submitted because view requires form.
Can anyone help me?
in:
Dropzone.options.myDropzone = {
....
actually myDropzone is the camelized version of the HTML element's ID.
so the form id must be my_dropzone instead of add-multiple:
<form id="my_dropzone" class="dropzone" action...

Categories