I have a javascript application (in angular) that calls my django application. It uses lists of integers to filter the response. In Django I'm using a form to clean the data.
Javascript:
app.factory('SearchData',
function(){
return {
shop:[],
sort:'',
xhr:'',
brand:[],
};
});
app.factory('SearchQuery',
['$http', '$location', '$route', 'SearchData',
function($http, $location, $route, SearchData){
return {
getItems: function(){
return $http.get('/search/',{
params: SearchData,
responseType: 'json',
});
}
};
}
]);
Python form:
class SearchForm(forms.Form):
shop = forms.IntegerField(widget=forms.SelectMultiple(),required=False)
sort = forms.CharField(max_length=1, min_length=1, required=False)
brand = forms.IntegerField(widget=forms.SelectMultiple(),required=False)
I get a list of integers in shop and brand but I do not how to handle it on the django side. I don't want to use MultipleChoiceField as I need to supply choices in form (which creates an unnecessary query). All I want to do is have a list of integers.
The form above throws "Enter a whole number.". I could just ditch the form and use request.GET.getlist('shop') (which works). But I'd rather use a form if possible...
Update, for now I'm using a MultipleChoiceField and pass the choices before validation in the view. Like:
shops = request.GET.getlist('shop', None)
sf = SearchForm(request.GET)
sf.fields['shop'].choices = shops
It works, but it isn't pretty.
Use a custom widget/field:
from django import forms
from django.core.exceptions import ValidationError
class MultipleValueWidget(forms.TextInput):
def value_from_datadict(self, data, files, name):
return data.getlist(name)
class MultipleValueField(forms.Field):
widget = MultipleValueWidget
def clean_int(x):
try:
return int(x)
except ValueError:
raise ValidationError("Cannot convert to integer: {}".format(repr(x)))
class MultipleIntField(MultipleValueField):
def clean(self, value):
return [clean_int(x) for x in value]
class SearchForm(forms.Form):
shop = MultipleIntField()
You can use TypedMultipleChoiceField from Django forms with coerce=int and to avoid validation against predefined list of choices override the def valid_value(self, value): method:
class MultipleIntegersField(forms.TypedMultipleChoiceField):
def __init__(self, *args, **kwargs):
super(MultipleIntegersField, self).__init__(*args, **kwargs)
self.coerce = int
def valid_value(self, value):
return True
class SearchForm(forms.Form):
shop = MultipleIntegersField()
Udi's code is good, but there is a problem (under Django 1.11.7) if you want to use this as (say) a hidden field of a completely general user-input form. The problem is that if the user input fails to validate and is re-POSTed with corrections, the multi-valued POST data comes back the second time around as a repr of itself, i.e
['a','b'] comes back as ["['a', 'b']"] and further mangled with each re-POST
So I wrote the following function which can be used to repair the damage every time the view processes POST data. It's a hack, because it involves making request.POST temporarily mutable using a private variable. Also it doesn't properly handle lists of strings containing commas, escaped quotes etc.
def sanitize_keys( request, only=None):
""" Restore multi-valued keys that have been re-posted. there's a repr
in the round trip, somewhere.
only = list of keys to sanitize. Default is all of them."""
mutt = request.POST._mutable
request.POST._mutable = True
keylist = only or request.POST.keys()
for key in keylist:
v = request.POST.get(key)
if v.startswith("[") and v.endswith("]"):
#print( "Debug: sanitizing " + v )
sanitized=[]
for s in v[1:-1].split(','):
s = s.strip()
if s.startswith("'") and s.endswith("'"):
s=s[1:-1].replace("\\'","'")
sanitized.append(s)
#print( "Debug: sanitized= ", sanitized )
request.POST.setlist( key, sanitized)
request.POST._mutable = mutt
return
Usage (fragments):
class TestForm( forms.Form):
name = forms.CharField()
...
customer_iid = MultipleValueField( required=False)
...
# POST
sanitize_keys( request, only=('customer_iid',) )
#print( 'Debug: customer_iid', request.POST.getlist('customer_iid', []) )
form = TestForm( request.POST)
Related
I have PATCH button form on ModelViewSet
class CompanyViewSet(viewsets.ModelViewSet):
serializer_class = s.CompanySerializer
queryset = m.Company.objects.all()
def patch(self, request, id, format=None):
print(id)
Now I try to modify the existing data id = 1
So I write this in textarea and push PATCH button.
{
"id":1,
"name": ""
}
However , there comes error like
patch() missing 1 required positional argument: 'id'
Maybe my json is wrong?? How can I do PATCH?
patch() missing 1 required positional argument: 'id'
Use perform_update like this :
class CompanyViewSet(viewsets.ModelViewSet):
serializer_class = s.CompanySerializer
queryset = m.Company.objects.all()
def perform_update(self, serializer):
instance = serializer.instance
request = self.request
serializer.save(**modified_attrs)
return Response(status=status.HTTP_200_OK)
I'm creating an app that shows a webpage search according to a random word that is chosen by the program. I uploaded a dictionary into python to get a random word and now I want to put this word into the src= in my javascript code. What I need is some kind of placeholder that connects the 2 languages
Python
if request.method == 'GET':
#create an empty dictionary
d = {}
key = 0
#open dictionary file and store it in 'dic'
with open('dictionaries/nouns.rtf',encoding="utf8", errors='ignore') as dic:
#read every line and give it a number
for line in dic:
value = line.replace('\\', '')
key += 1
d[key] = value
#select a random number in the range of the dictionary (and store it in a variable)
rand = random.randrange(1, len(d) + 1)
#get the word with that number (and store it in a variable)
word = d[rand]
#print(word)
return render_template('/player.html', word = word)
Javascript
<script>
let is = document.getElementById('innerSpace');
query = encodeURI({{word}})
is.src = `https://www.bing.com/images/search?q=weirdest+${query}&form=QBLH&sp=-1&pq=cats&sc=8-4&qs=n&cvid=20659354CDFD49C6B03ED29A4F35EC64&first=1&tsc=ImageBasicHover`
</script>
To get the randomly generated word from Python to Javascript, you need to return a JSON response from your view.
from django.http import JsonResponse
if request.method == 'GET':
...
data = { 'word': word}
return JsonResponse(data)
You can then access the word within JavaScript using AJAX or Fetch. I will use Jquery as an example.
let is = document.getElementById('innerSpace');
$.ajax({
url: '/URL_for_your_view/',
action: 'GET',
// when the server returns data, the success method is run
success: function (data) {
query = encodeURI({{data.word}})
is.src = `https://www.bing.com/images/search?q=weirdest+${query}&form=QBLH&sp=-1&pq=cats&sc=8-4&qs=n&cvid=20659354CDFD49C6B03ED29A4F35EC64&first=1&tsc=ImageBasicHover`
}})
Now, the problem with this solution is that the view will no longer return the template player.html which seems essential in your code.
return render_template('/player.html', word = word)
To my knowledge, you cannot return a JSON response and a template in the same view. It's impossible.
So you need to use JavaScript to recreate the player.html code inside the success method and append it to the DOM.
I'm developing a Grails web application whose modules, mostly, must implement a master-detail interface. As an approach, I want to put the following code to your consideration:
Master Class:
import org.codehaus.groovy.grails.web.json.JSONArray
class MyForm {
String name
String address
String detail
BigDecimal total
static hasMany = [details: MyFormDetail]
static constraints = {
name()
address()
detail()
total()
}
static mapping = {
detail type: 'text'
}
def beforeInsert = {
def detailJSON = new JSONArray(detail)
detailJSON.each {
def quantity = it.getAt("quantity").toString().toBigDecimal()
def description = it.getAt("description").toString()
def unitaryPrice = it.getAt("unitaryPrice").toString().toBigDecimal()
def subtotal = it.getAt("subtotal").toString().toBigDecimal()
def myFormDetail = new MyFormDetail(
quantity: quantity,
description: description,
unitaryPrice: unitaryPrice,
subtotal: subtotal
)
this.addToDetails(myFormDetail)
}
}
}
Detail Class:
class MyFormDetail {
Integer quantity
String description
BigDecimal unitaryPrice
BigDecimal subtotal
static belongsTo = [myForm: MyForm]
static constraints = {
quantity()
description()
unitaryPrice()
subtotal()
}
}
myFormUtilities.js helper file:
$(document).ready(function() {
$("#detailTable").jqGrid({
datatype: "local",
height: 100,
colNames: ["QUANTITY","DESCRIPTION","UNIT. PRICE","SUBTOTAL"],
colModel:[
{name:'quantity',index:'quantity', width:100},
{name:'description',index:'description', width:400},
{name:'unitaryPrice',index:'unitaryPrice', width:100},
{name:'subtotal',index:'subtotal', width:100}],
caption: "DETAIL"
});
createTable();
$("#addRow").bind("click",addRow);
$("#removeRow").bind("click",removeRow);
$("#quantity, #unitaryPrice").bind("keyup",calculateTotal);
function calculateTotal(){
let quantity = parseFloat($("#quantity").val());
let unitaryPrice = parseFloat($("#unitaryPrice").val());
let subtotal = quantity*unitaryPrice;
$("#subtotal").val(subtotal);
}
function addRow(){
let row = new Object();
row.quantity = $("#quantity").val();
row.description = $("#description").val();
row.unitaryPrice = $("#unitaryPrice").val();
row.subtotal = $("#subtotal").val();
let detailJSON = ($("#detail").val()=="")?"[]":$("#detail").val();
let mydata = $.parseJSON(detailJSON);
mydata.push(row);
$("#detail").val(JSON.stringify(mydata));
createTable();
}
function removeRow(){
let filaId = parseInt($('#detailTable').jqGrid('getGridParam','selrow')) - 1;
let mydata = $.parseJSON($("#detail").val());
let nuevoMydata = new Array();
for(let i=0;i<mydata.length;i++){
if(filaId!=i)
nuevoMydata.push(mydata[i]);
}
$("#detail").val(JSON.stringify(nuevoMydata));
createTable();
}
function createTable(){
let total = 0;
let aRow = new Object();
let detailJSON = ($("#detail").val()=="")?"[]":$("#detail").val();
let mydata = $.parseJSON(detailJSON);
$("#detailTable").jqGrid("clearGridData", true);
for(let i=0;i<mydata.length;i++){
aRow = mydata[i];
total += parseFloat(aRow.subtotal);
$("#detailTable").jqGrid('addRowData',i+1,aRow);
}
$("#total").val(total);
}
});
This is the displayed form (I know it's an autogenerated view but see it as a very basic GUI mockup, please):
So, these are the issues:
Both Subtotal and Total fields are calculated fields that were set
read-only to prevent user to modify their content, but I found that
using the browser element inspector their content and properties
(like read-only) can be modified.
Same happens with detail. If its content is altered, a server side
error will be produced when the instance is saved because the beforeInsert block needs a valid
JSON string to create the detail instances. This field is also used to generate the detail JqGrid. This field will be
hidden.
Everything with this example is working as expected but a fault could be caused by a curious user.
Is there a way to prevent these fields text to be changed even using
the inspector?
Is there another place to keep this data safe from alterations?
I'm using this detail field to save a JSON string and this is my solution to implement the master-detail pattern. Is there another
way to improve this implementation avoiding the need to define a
field for this string?
If your users have access to the inspector/web console, then they have access to all the client-side data that you do (they are only limited by their knowledge of how to use the browser's dev tools). They can change any element's text or input's value. Thus, the only place that would be 100% safe to store this data is on the server side.
What you can try, which would at least make it a lot more work for them to update anything, and might prevent the change events calling the server, is to hide each input (with CSS for instance) and replace it in the visible HTML with a <span> element (or some other non-form element). Then make sure that whenever the <input> value is changed, you update the text of the corresponding span. Something like:
$("#subtotalSpan").html( $("#subtotal").val() );
added inside the calculateTotal function. If you want them to not even be able to edit the HTML of a span, you could go even further and draw the value to a <canvas> instead of a <span>. They won't be able to change text drawn into a canvas element.
Sorry, there is no easy answer for protecting the data sent from the browser. Any user-submitted input should be treated as dirty by your server.
Data sent in a POST or GET can always be altered on the client side or even created wholesale (with something like CURL or Postman). For that reason, you should always validate the user input on the server side and compute your totals there.
If you have properties that you don't want to participate in the mass property data binding you should configure them with bindable: false.
You have this:
class MyFormDetail {
Integer quantity
String description
BigDecimal unitaryPrice
BigDecimal subtotal
static belongsTo = [myForm: MyForm]
static constraints = {
quantity()
description()
unitaryPrice()
subtotal()
}
}
You could change that to something like this:
class MyFormDetail {
Integer quantity
String description
BigDecimal unitaryPrice
BigDecimal subtotal
static belongsTo = [myForm: MyForm]
static constraints = {
quantity()
description()
unitaryPrice bindable: false
subtotal bindable: false
}
}
If you have something like this:
MyFormDetail mfd = new MyFormDetail()
// The body of the request will be read and
// bound to mfd but subtotal and unitaryPrice
// will NOT be updated...
mfd.properties = request
If you do that, you can still assign values those properties directly:
mfd.subtotal = // ... some value here
Another option is to submit your form to a controller action which accepts a command object. That CO could look like this...
class FormCommand {
Integer quantity
String description
}
If you have an action that looks like this:
class SomeController {
def someAction(FormCommand co) {
// it won't matter if subtotal
// and unitaryPrice are in the
// request, the co only has the
// the properties you want so now
// you can use those to update the
// corresponding persistent entity
// in a safe way
}
}
Note: The question and answers to this post were edited along the way. If the comments do not make sense, it is because the original question and answers were different. I think it still has a lot of good info, though.
Update: I am brand new to Ruby. After trying some of the solutions recommended on this post, I found that the .to_json method does work on nested hashes and arrays. The issue is that it does not work on nested hashes and arrays, if they are instances of a class. I have data that is nested class instances, and when I parse the JSON on the client-side, it gives me this:
["#<Cake:0x00007fc37b19f988>", "#<Cake:0x00007fc37b19f5c8>"]
0 : "# <Cake:0x00007fc37b19f988>"
1 : "#<Cake:0x00007fc37b19f5c8>"
(It's just strings with object id's)
If anyone can help point me in the right direction? Thank you!
Original Question:
I am using sinatra/Ruby on the backend, and I am using an ajax call on the front-end.
I am not able to parse the JSON object once it gets to the front end. I saw that the JSON was only parsing one level deep. In other words, if I have an array of objects, I could get those objects, but I could not get any nested objects; so, I wrote a recursive function to try to properly format the nested objects, but they still won't parse.
Can anyone help me figure out how to send JSON from Ruby to an AJAX request.
This is my Ruby code:
require 'json'
sample_data = [
{ :category => 'muffin',
:flavor => 'chocolate',
:ingredients => {
:dairy => ["milk", "butter"],
:grain => ["wheat"]
}
},
{ :category => 'muffin',
:flavor => 'blueberry',
:ingredients => {
:dairy => ["milk", "butter"],
:grain => ["wheat"]
}
}]
#recursive function that applies .to_json to every nested array or object:
def jsonify(data)
if data.class == Hash
container = {}
data.each do |key, value|
if value.class != Hash && value.class != Array
container[key.to_sym] = value
else
container[key.to_sym] = jsonify(value)
end
end
elsif data.class == Array
container = []
index = 0
while index < data.length
value = data[index]
if value.class != Hash && value.class != Array
container[index] = value
else
container[index] = jsonify(value)
end
index += 1
end
end
container.to_json
end
This is what my route is sending:
jsonify(sample_data)
Formatted my Ajax request as a promise(but don't think that's relevant?)
requestPromise("http://localhost:4567/jsondata","GET")
.then(data => {
let parsedData = JSON.parse(data);
console.log(parsedData)
})
.catch(err => {
console.log(err)
})
But, on the client-side, I just get this:
(2) ["{"category":"muffin","flavor":"chocolate","ingredi…\"butter\\"]\",\"grain\":\"[\\"wheat\\"]\"}"}", "{"category":"muffin","flavor":"chocolate","ingredi…\"butter\\"]\",\"grain\":\"[\\"wheat\\"]\"}"}"]
All of my nested objects are still strings, and have a bunch of escape characters.
Can anyone please help me to figure out what I am doing wrong, and how to do it correctly. Thank you!
Your jsonify method is hurting you.
You should call sample_data.to_json directly and let the JSON module deal with the nesting.
EDIT (JSON with custom data type):
To support #to_json within your custom class / object, you need to add a #to_json method to your class. i.e.:
require 'json'
class MyClass
def initialize name, age
#name = name
#age = age
end
def to_json(*a)
{name: #name, age: #age}.to_json(*a)
end
end
[ MyClass.new("John", 24) ].to_json
I am relatively new to Django framework and working on getting my first application running. I am encountering the below issue when i try to pass my queryset from my view to the template.
My view.py:
with connection.cursor() as cursor:
cursor.execute("SELECT TOP(5) * FROM xxx WHERE sbu = %s", [sbu])
def dictfetchall(cursor):
columns = [col[0] for col in cursor.description]
return [dict(zip(columns,row)) for row in cursor.fetchall()]
results = dictfetchall(cursor)
class DecimalEncoder(json.JSONEncoder):
def _iterencode(self, o, markers=None):
if isinstance(o, decimal.Decimal):
return (str(o) for o in [o])
return super(DecimalEncoder, self)._iterencode(o, markers)
json_result = json.dumps(data, cls=DecimalEncoder)
I end up with the below error:
Decimal('28.80') is not JSON serializable
Any suggestions? Am i missing a step somwhere? I have lot of decimal values in the queryset.