I am doing filter using GET method and I would like to be able to remove only specific filters, how do I do that?
Now I have a form with token fields so I get id's of countries and towns:
<form id="search" method="GET" action="{% url 'selection'%}">
<div class="bg-grey text-center">
<input type="text" class="form-control" id="tokenfield1" name="country">
</div>
{% if filteredcountries %}
<table class="table table-list">
{% for c in filteredcountries %}
<tr>
<td><button type="button" class="btn btn-xs btn-danger" data-remove="{{c.id}}"><i class="fas fa-times"></i></button></td>
<td>{{c.country_name`}}</td>
</tr>
{% endfor %}
</table>
{% endif %}
<div class="bg-grey text-center">
<input type="text" class="form-control" id="tokenfield2" name="town">
</div>
{% if filteredtowns %}
<table class="table table-list">
{% for t in filteredtowns %}
<tr>
<td><button type="button" class="btn btn-xs btn-danger" data-remove="{{t.id}}"><i class="fas fa-times"></i></button></td>
<td>{{t.town_name}}</td>
</tr>
{% endfor %}
</table>
{% endif %}
</form>
views.py:
def hotel_view(request):
countries = request.GET.get('country')
if not countries:
countries = []
else:
countries = countries.split(', ')
towns = request.GET.get('town')
if not towns:
towns = []
else:
towns = towns.split(', ')
results = Hotel.objects.filter(Q(town_id__in = towns) | Q(country_id__in = countries))
context = {
'filteredcountries': countries,
'filteredtowns': towns,
'hotels': results
}
return render(request, 'myapp/hotels.html', context)
Now I want to click the button and remove the specific town or country from the GET and remain the others. Can you lead me to the solution?
example URL before button click:
..myapp/hotels?country=**2**%2C+**4**&town=2
url after button click with country id 2:
..myapp/hotels?country=**4**&town=**2**
or after button click with town id 2:
..myapp/hotels?country=**2**%2C+**4**&town=
This is a solution around using javascript and JQuery. The basic approach is as follows:
Store the current window.location.href in a variable on loading of page. Why?
Otherwise each manipulation of it would result in sending a new GET
request which is probably undesirable
Manipulate the string stored
in the mentioned variable depending on the button clicked.
Preventing the submit button from firing directly but using the
manipulated string
Here the script part with some comments:
<script>
var current_get = window.location.href // Store current url
var handleget = function(part_chooser, id){
// part_chooser = country or town, id = pk to remove
var parts = current_get.split('?');
if (parts.length < 2){ // No get params to manipulate
return;
}
var get_part = parts[1];
var get_parts =get_part.split('&');
var i; // To keep order of new href if important
var str_to_handle; // Part to manipulate
for (i = 0; i < get_parts.length; i++){
if (get_parts[i].startsWith(part_chooser)){
str_to_handle = get_parts[i];
break;
}
};
// Do manipulation and store new url
var get_arr = str_to_handle.split('=');
if (get_arr.length > 0){
params = get_arr[1].split('%2C+');
var index = params.indexOf(id.toString());
if (index >= 0){
params.splice(index, 1);
params = params.join('%2C+');
get_parts[i] = part_chooser + '=' + params;
current_get = parts[0]+'?'+get_parts.join('&');
console.log(current_get);
}
};
};
$('button[type=submit]').click(function(event){
event.preventDefault();
window.location.href = current_get;
})
$('.country-btn').click(function(){
var id = $(this).data('remove');
handleget('country', id);
});
</script>
Some remarks
I intentionally did not use long chains of JS to shorten the code as IMHO it is easier to read step by step
The string manipulation can probably be shortend by clever use of regular expressions.
If your tokenfields contain all the possible ids as a comma seperated list on loading a javascript for deleting the specific id from the tokenfield could look like this:
<script>
$('.country-btn').click(function(){
var id = $(this).data('remove');
var countries = $('#tokenfield1').val().split(',');
var index = countries.indexOf(id.toString());
if (index >= 0){
countries.splice(index, 1)
}
$('#tokenfield1').val(countries.join());
})
</script>
In order to fill your tokenfield with the ids returned in the context you could do something like
<input type="text" class="form-control" id="tokenfield1" name="country"
value="{% for c in filteredcountries %}{{c.id}}{% if not forloop.last %},{% endif %}{% endfor %}">
Related
I have a store page on my site (written in Django w/ a Passenger server) where users can add products to their shopping cart. The cart is stored as a cookie. When they proceed from the store page to the cart page, the cart view should list all of the items in their cart (which it gets from the cart cookie in the request). This works fine when I run it locally. However, when I run this in production, it almost always says the cart is empty. It only lists the cart items properly if I do a hard refresh of the page.
I've added some print statements to the server, and I can see that the view for the page is being called twice in prod (it's only called once in dev). The first time the view is called, the cart cookie has the correct values. The second time it's called however, the cart cookie in the request is an empty object {}. All other cookies in the request look normal (session id, csrftoken, etc). What's very strange is I can see in the browser's developer panel that the cart cookie is populated in both the request's header cookie tab as well as the storage tab.
Django view/utility functions:
def cart_view(request):
data = cart_data(request)
context = {
'items': data['items'],
'order': data['order'],
'cart_items': data['cart_items'],
}
return render(request, 'store/cart.html', context)
def cart_cookie(request):
try:
cart = json.loads(request.COOKIES['cart'])
except:
cart = {}
return cart
def cart_data(request):
cart = cart_cookie(request)
items = []
order = {'get_cart_total': 0, 'get_cart_items': 0, 'shipping': False}
cart_items = order['get_cart_items']
'''
Logic to parse the cart cookie
'''
return {
'items': items,
'order': order,
'cart_items': cart_items,
}
Here's the functions on the store page to populate the cart:
var updateBtns = document.getElementsByClassName('update-cart');
for (var i = 0; i < updateBtns.length; i++) {
updateBtns[i].addEventListener('click', function() {
var productId = this.dataset.product;
var action = this.dataset.action;
updateCartCookie(productId, action);
})
}
function updateCartCookie(productId, action) {
if (action == 'add') {
if (cart[productId] === undefined) {
cart[productId] = {'quantity':0};
}
cart[productId]['quantity'] += 1;
} else if (action == 'remove') {
cart[productId]['quantity'] -= 1;
if (cart[productId]['quantity'] <= 0) {
delete cart[productId];
}
}
document.cookie = 'cart=' + JSON.stringify(cart) + ";domain=;path=/;SameSite=Strict;Secure;";
location.reload();
}
This code is in the page header to initialize the cookies. It's not being called on navigation to the cart page.
<script type="text/javascript">
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++){
var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1))
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken'); // this is just a string
var cart = JSON.parse(getCookie('cart')); // this is a json object, so we need to parse it
if (cart == undefined) {
cart = {};
document.cookie = 'cart=' + JSON.stringify(cart) + ";domain=;path=/;SameSite=Strict;Secure;";
}
</script>
And finally, here are the templates for the Store and Cart pages
#Store
{% block content %}
{% load static %}
<div class="container">
<h1 class="page-title">Store</h1>
<div class="row" style="margin-bottom: 20px">
{% for product in products %}
<div class="col-lg-4" style="margin-bottom: 20px">
<img class="thumbnail" src="{{product.imageURL}}">
<br>
<div class="box-element product">
<div>
<h4><strong>{{product.name}}</strong></h4>
<hr>
<p>
{{product.description}}
</p>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<h4>{{ product.price|floatformat:-2 }}</h4>
<button data-product={{product.id}} data-action="add" class="btn light update-cart">Add</button>
</div>
</div>
</div>
{% endfor %}
</div>
<div>
<a style="float: right; margin: 5px;" class="btn dark" href="{% url 'cart' %}">Cart</a>
</div>
</div>
{% endblock content %}
#Cart
{% block content %}
<div class="container">
<h1 class="page-title">Cart</h1>
<div class="">
<a class="btn button light" href="{% url 'store' %}">← Store</a>
<br>
<br>
{% if cart_items == 0 %}
<p style="text-align: center;">Your cart is empty.</p>
{% else %}
<div>
<div class="cart-row">
<div class="shrinking-flex-column-2-1"><strong>Item</strong></div>
<div class="static-flex-column-1"><strong>Price</strong></div>
<div class="static-flex-column-1"><strong>Quantity</strong></div>
<div class="static-flex-column-1"><strong>Total</strong></div>
</div>
{% for item in items %}
<div class="cart-row" style="align-items: center;">
<div class="shrinking-flex-column-2-1">{{item.product.name}}</div>
<div class="static-flex-column-1">${{item.product.price}}</div>
<div class="static-flex-column-1">
<p class="quantity">x{{item.quantity}}</p>
<div class="quantity">
<img class="chg-quantity update-cart" src="{% static 'store/images/arrow-up.png' %}" data-product={{item.product.id}} data-action="add" >
<img class="chg-quantity update-cart" src="{% static 'store/images/arrow-down.png' %}" data-product={{item.product.id}} data-action="remove" >
</div>
</div>
<div class="static-flex-column-1">${{item.get_total}}</div>
</div>
{% endfor %}
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<h5>Total Order Items: <strong>{{order.get_cart_items}}</strong></h5>
<h5>Total Order Amount: <strong>${{order.get_cart_total}}</strong></h5>
</div>
<a class="btn dark" role="button" href="{% url 'checkout' %}">Checkout</a>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock content %}
This ended up being entirely a caching issue. Adding the #never_cache decorated to the cart/checkout views fixed the problem.
How to set cache control headers in a Django view class (no-cache)
I want to iterate over an object I return from django view to create dynamically columns in a table (each column is a field in a model and I want to be able to switch the model without changing the template)
In the html body part of the template the same exact code worked fine, in the script section it's not.
My guess is the zip object pass as iterator that can consumed only once? if so how do I make django send a normal list object?
This is my view:
def HelperColumnesFieldsAndNames(data):
columns = { '<ManyToOneRel: client.paymentdata>':["unimp_pay","unimplemented"],
'<ManyToOneRel: client.subscribtiondata>':["subscribtiondata","unimplemented"],
'<ManyToOneRel: client.calldata>':["calldata","unimplemented"],
'<ManyToOneRel: client.extracommunicationdata>':["extracommunicationdata","unimplemented"],
'client.Client.creation_date':["creation_date","Creation Date"],
'client.Client.first_name':["first_name","First Name"],
'client.Client.last_name':["last_name","Last Name"],
'client.Client.address':["address","Address"],
'client.Client.city':["city","City"],
'client.Client.phone_number':["phone_number","Main Phone"],
'client.Client.creator_of_user':["creator_of_user","Client Creator"],
'client.Client.status':["status","Status"],
'client.Client.status_change_time':["status_change_time","Last Time Status Changed"],
'client.Client.allocated':["allocated","Currently Allocated To Talpan"],
'client.Client.group':["group","Owner Group"],
'client.Client.tags':["tags","Tags"],
'client.Client.tagged_items':["tagged_items","Tagged Item"],
}
column_name = []
column_field = []
for field in data:
field = str(field)
if field in columns.keys():
column_field.append(columns[field][0])
column_name.append(columns[field][1])
return zip(column_name, column_field)
# view function
def AllClientts(request):
user_groups = []
for groups in request.user.groups.all():
if groups.name != MANGER_GROUP_NAME:
user_groups.append(groups)
# get all object that belongs to the requested user groups
tableData = Client.objects.filter(group__in=user_groups)
return render(request, "client/clients.html", {"objects": tableData, "columns_name":HelperColumnesFieldsAndNames(Client._meta.get_fields()) })
Working example non in the script section
{% block content %}
<div class="d-flex pt-5 pb-2">
<!-- Need to be set to colums initials names using django -->
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Show
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" id="swichable_column">
<!--reate fileds name and Showable names -->
{% for name in columns_name %}
<div class="checkbox">
<label>
<input type="checkbox" checked="checked" class="chackable_name" value="{{ name.1 }}">{{ name.0 }}</label>
</div>
{% endfor %}
</div>
</div>
</div>
<div id="example-table"></div>
{% endblock content %}
Script section in my tamplet - not working even do it's the same code
<script>
//define some sample data
var tabledata = [
{id:1, first_name:"Oli Bob", age:"12", col:"red", dob:""},
{id:2, name:"Mary May", age:"1", col:"blue", dob:"14/05/1982"},
{id:3, name:"Christine Lobowski the gratest and latest", age:"42", col:"green", dob:"22/05/1982"},
{id:4, name:"Brendon Philips", age:"125", col:"orange", dob:"01/08/1980"},
{id:5, name:"Margret Marmajuke", age:"16", col:"yellow", dob:"31/01/1999"},
];
$(function() {
var table = new Tabulator("#example-table", {
height:205, // set height of table (in CSS or here), this enables the Virtual DOM and improves render speed dramatically (can be any valid css height value)
data:tabledata, //assign data to table
layout:"fitColumns",
pagination:"local",
paginationSize:6,
paginationSizeSelector:[3, 6, 8, 10],
columns:[ //Define Table Columns
{% for name in columns_name %}
{title:"{{ name.0 }}", field:"{{ name.1 }}", align:"center", cellClick:function(e, cell){alert("cell clicked - " + cell.getValue())}},
{% endfor %}
]});
</script>
Apparently if I return zip object directly it's itearable and can be consume once only.
I replace zip() with list(zip()) and it's solved that issue
Ok so I need some help. I am trying to have the address field from my CreateView form auto populate with the Places API result. So I see 2 options:
Override the address field in the CreateView. Use the address input for Places API as the input into the address field for my model.
Autocomplete the address field in the form, from the output of the address input (Places API lookup).
Any suggestions would be greatly appreciated. I have tried multiple different options and cant seem to get it to actually work correctly.
Thanks.
autocomple.html
var placeSearch, autocomplete;
var componentForm = {
street_number: 'short_name',
route: 'long_name',
locality: 'long_name',
administrative_area_level_1: 'short_name',
country: 'long_name',
postal_code: 'short_name'
};
function initAutocomplete() {
// Create the autocomplete object, restricting the search predictions to
// geographical location types.
autocomplete = new google.maps.places.Autocomplete(
document.getElementById('autocomplete'), {types: ['geocode']});
// Avoid paying for data that you don't need by restricting the set of
// place fields that are returned to just the address components.
autocomplete.setFields(['address_component']);
// When the user selects an address from the drop-down, populate the
// address fields in the form.
autocomplete.addListener('place_changed', fillInAddress);
}
function fillInAddress() {
// Get the place details from the autocomplete object.
var place = autocomplete.getPlace();
console.log(place)
for (var component in componentForm) {
document.getElementById(component).value = '';
document.getElementById(component).disabled = false;
}
// Get each component of the address from the place details,
// and then fill-in the corresponding field on the form.
for (var i = 0; i < place.address_components.length; i++) {
var addressType = place.address_components[i].types[0];
if (componentForm[addressType]) {
var val = place.address_components[i][componentForm[addressType]];
document.getElementById(addressType).value = val;
}
}
}
// Bias the autocomplete object to the user's geographical location,
// as supplied by the browser's 'navigator.geolocation' object.
function geolocate() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
var circle = new google.maps.Circle(
{center: geolocation, radius: position.coords.accuracy});
autocomplete.setBounds(circle.getBounds());
});
}
}
views.py
class PropertyCreateView(CreateView):
model = Property
fields = ['address', 'postal_code', 'price',
'details', 'sales_status', 'property_type']
def form_valid(self, form):
return super().form_valid(form)
detail.html
{% load crispy_forms_tags %}
{% block content%}
<div class="container">
<div class="card o-hidden border-0 shadow-lg my-2">
<div class="card-body p-0">
<div class="row">
<div class="col-lg-12">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">New Property</h1>
</div>
<form method="POST">
{% csrf_token %}
<div class="form-group">
<label>Address: </label>
<div id="locationField">
<input id="autocomplete"
placeholder="Enter your address"
onFocus="geolocate()"
type="text"
name="full_address"
class="form-control"
/>
</div>
</div>
<fieldset class="form-group">
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-primary btn-user btn-block"" type="submit">Add Property</button>
</div>
</form>
<hr>
</div>
</div>
</div>
</div>
</div>
</div>
{% include 'properties_app/autocomplete.html' %}
I have read this post: How can I create a text link in a Knockout javascript table? along with a couple others.
But, I am missing something somewhere, or not taking the right approach. I've included the relevant chunks of code for my problem. I am trying to use the table generated by knockout to to either update a task or remove a task. The remove part is working fine. I am trying to get the update to link to another page that is used to update the task. I cannot figure out what I need to do to get the link working properly in the update column.
I've tried several different approaches for how to put the url in the list of dictionaries that is passed to the KO model. Any advice to steer me in the right direction? If I am missing any information, please let me know. Thank you.
Views.py
def TaskList(request, job_id):
job_tasks = Tasks.objects.filter(parent=job_id)
tasks_list = []
for task in job_tasks:
task_row = {}
task_row['task_id'] = task.task_id
task_row['t_name'] = task.name
task_row['date'] = task.date_created
task_row['state'] = task.state
task_row['url'] = '{% url tracking:update_task task_id=task.task_id %}'
tasks_list.append(task_row)
json_tasks = json.dumps(tasks_list)
if request.POST:
json_data = request.POST.get('json_blob')
obj = loads(json_data)
task.task_id = obj.get("task_id")
remove_task = Tasks.objects.get(task_id=task.task_id)
remove_task.delete()
messages.success(request, 'Task removed')
HTML
<table>
<thead>
<th>Name</th>
<th>Date</th>
<th>State</th>
<th>Update</th>
<th>Remove</th>
</thead>
<tbody data-bind "foreach: tasks">
<tr>
<td data-bind="text: t_name"></td>
<td data-bind="text: date"></td>
<td data-bind="text: state"></td>
<td a class="btn" data-bind="attr: {href: url}">Update</a></td>
<td button class="btn" data-bind="click: $root.remove_task">Remove</button></td>
</tr>
</tbody>
</table>
{% block javascript_variables_nocompress %}
window.TASKS = {{ json_tasks|safe }};
{% endblock %}
{% block javascript_compress %}
<script type='text/javascript' src="{% static 'js/knockout/knockout.js' %}"></script>
<script type="text/javascript">
$(function() {
var RemoveTaskModel = function () {
var self = this;
self.tasks = ko.observableArray(window.TASKS);
self.remove_task = function(task) {
self.tasks.remove(task);
$("#json_blob").val(ko.toJSON(task));
}
}
ko.applyBinding(new RemoveTaskModel());
});
</script>
{% endblock %}
HTML
I would use reverse to do a reverse lookup of the URL for each task:
from django.core.urlresolvers import reverse
def TaskList(request, job_id):
job_tasks = Tasks.objects.filter(parent=job_id)
tasks_list = []
for task in job_tasks:
...
task_row['url'] = reverse('update_task', args=(),
kwargs={'task_id': task_id})
Then your observableArray should be able to bind the property from the JSON to the anchor tag. You might also note that in your code sample, your td is malformed:
<td a class="btn" data-bind="attr: {href: url}">Update</a></td>
it should be:
<td><a class="btn" data-bind="attr: {href: url}">Update</a></td>
Using django, I have a form in which a user enters his 'position'. The user may add multiple positions. The user may also delete positions. There are two things worth noting with this:
1) in the form, there are two buttons, 'Add' and 'Delete'.
2) I am using a for loop in the template to populate the list of positions and delete buttons.
This is what I currently have:
# in template
<tr>
<td>Position</td>
<td>{{ form.position }}
<input type="submit" value="Add" , name='action'/>
</td>
</tr>
<tr>
<td> </td>
<td>
{% for position in positions %}
{{ position}}
<input type="submit" value="Delete {{position }}", name='action'/>
{% endfor %}
</td>
</tr>
# in views.py
...
if action == 'Add':
positions.append(request.POST['position'])
return render_to_response(...)
if 'Delete' in action:
positions.remove(request.POST['action'][7:])
return render_to_response('...)
This seems like a very inelegant way to do the "Deletion" part.
Is there a better way to get the value of the position, without having to cram in additional information in the 'Delete' submit button, and then slicing it off to get its value?
I see three options here:
Use checkbox field for each position and one "Delete" button. In that case a user can choose multiple positions to be deleted and you can get their IDs from request easily.
Use a hidden field position and a little bit of Javascript to fill it. If you use jquery it could be:
<input type="hidden" name="position" value="" />
{% for position in positions %}
<input type="submit" value="Delete" name="action" data-position="{{ position }}" />;
{% endfor %}
<script type="text/javascript">
var $position_input = $("input[name='position']");
$("input[name='action'][value='Delete'].click(function(e) {
var $this = $(this);
var position = $this.data("position");
$position_input.val(position);
});
</script>
Insert position ID into name attribute, like this:
<input type="submit" value="Delete" name="delete-position.{{ position }} />
In view function you'll have to look through all data in request.POST and find all items which start with delete-position and then use slicing.