I am exploring the use of CherryPy as back-end and emberjs as front-end for a simple web-application that manages a list of books. Cherrypy simply serves a handlebars template upon a request for index:
import os
import cherrypy
from google.appengine.api import users
from google.appengine.ext import ndb
class Root:
def __init__(self):
# book REST API
self.books = BookAPI()
#cherrypy.expose
def index(self):
return open(os.path.join(template_env, 'index.hbs'))
And I use classes BooksAPI and Books to serve as RESTfull API that employs the Google data storage for storing book objects (I only store isbn now).
class BookAPI():
exposed=True
#cherrypy.tools.json_out()
def GET(self, isbn=None):
# get the current user
user = users.get_current_user()
if(isbn is None):
# query all books current user
ancestor_key = ndb.Key("Library", str(user.user_id()))
books = Book.query_books(ancestor_key).fetch(20)
# convert to JSON list of books
book_list = []
for index, b in enumerate(books):
book_list.append({'id': index, 'isbn': b.isbn})
result = {
"books": book_list
}
return result
def POST(self, isbn):
# get the current user
user = users.get_current_user()
# create book and save in data storage
parent_key = ndb.Key('Library', user.user_id())
book = Book(parent=parent_key, isbn=isbn)
book.put()
...
class Book(ndb.Model):
isbn = ndb.StringProperty()
#classmethod
def query_books(cls, ancestor_key):
return cls.query(ancestor=ancestor_key)
For the emberjs clientside I use the RESTAdapter:
window.Books = Ember.Application.create();
Books.ApplicationAdapter = DS.RESTAdapter.extend();
My emberjs book model is defined as follows:
Books.Book = DS.Model.extend({
isbn: DS.attr('string'),
});
And I added the following book controllers:
Books.BookController = Ember.ObjectController.extend({
actions: {
removeBook: function() {
var book = this.get('model');
book.deleteRecord();
book.save();
}
}
});
Books.BooksController = Ember.ArrayController.extend({
actions: {
createBook: function() {
// get book isbn
var isbn = this.get('newIsbn');
if(!isbn.trim()) { return; }
// create new book model
var book = this.store.createRecord('book', {
isbn: isbn,
});
// clear the 'new book' text field
this.set('newIsbn', '');
// Save model
book.save();
}
}
});
And finally the following routes:
Books.Router.map(function () {
this.resource('books', { path: '/' });
});
Books.BooksRoute = Ember.Route.extend({
model: function() {
return this.store.find('book');
}
});
Adding and deleting books using the FixedAdapter worked, then I switched to the RESTAdapter.
The GET method worked. Emberjs automatically invokes the GET method and successfully obtains a list of books in JSON format that are displayed in the index.hbs template.
However, emberjs calls the POST method in a way I did not expect. It seems that ember sends an empty POST, without the isbn added as POST data. Because when I remove the isbn keyword argument from the cherrypy POST function, the function does get called. I need the isbn though, to create the book object.
I am probably forgetting something obvious here, but I cannot figure out what. Does anyone has an idea what I am forgetting or doing wrong? Thanks.
Bastiaan
For saving new records Ember send a json representation of the object being saved in the post body...
In your case sholud be
book:{isbn:[the isbn value]}
So there is no isbn parameter
Can you test with this on your post function
def POST(self):
# get the current user
user = users.get_current_user()
cl = cherrypy.request.headers['Content-Length']
rawbody = cherrypy.request.body.read(int(cl))
body = simplejson.loads(rawbody)
bookFromPost = body.book
# create book and save in data storage
parent_key = ndb.Key('Library', user.user_id())
book = Book(parent=parent_key, isbn=bookFromPost.isbn)
book.put()
You should return a 201 created HTTP code with a json representation of the book with assigned id
book:{id:[the new id],isbn:[the isbn value]}
Related
VIEW:
class AddFoo(BSModalCreateView):
template_name = 'qualitylabs/add_foo.html'
form_class = AddFooForm
success_message = 'Success: Foo was created'
success_url = reverse_lazy('Home')
FORM:
class AddFooForm(BSModalModelForm):
class Meta:
model = Foo
fields = '__all__'
widgets = {
'part_number': PartNumberWidget,
'operation': OperationNumberWidget,
}
JAVASCRIPT:
function sendToServer(machine, before, after) {
var modified_form_data = before + "&inspection_machine=" + encodeURIComponent(machine) + after
$.ajax({
type: $('#AddFooForm').attr('method'),
url: $('#AddFooForm').attr('action'),
data: modified_form_data,
success: function (data) {
console.log('did it!!!!!')
}
});
}
I'm trying to post a form to the server, which should populate the database. I have to send it in Ajax because I have to iterate multiple times, changing variables each time (poorly set up database). The weirdest thing is that when I run the code, I get:
"POST /add_foo/ HTTP/1.1" 302 0
which is the same result that you get when the server responds properly. The page does not redirect to the success_url, and when I check the data in the admin page, the items have not been added. However, in the admin page I do get the success message of "Sucess: Foo was created"
Any ideas? It's quite strange.
I was copying off of some previous code that had used BSModalModelForm because they were using modal forms, but I wasn't. Once I changed it to CreateView it worked.
Objective is to accept UI multiple parameters and give it to the model (127.0.0.1:5002)using flask API and then the scoring from the model post back to UI (127.0.0.1:5001)
I am getting error (which is posted below at the end) when a model accept the value from the UI.
So i am posting values to 127.0.0.1:5002 where model takes that as 1 json object but i am getting error.
So i post 1 json object from this code (let me know if there is an issue in the code- i am a newbie)
<script>
$(function() {$('#analysis').bind('click', function() {
$.post('http://127.0.0.1:5002/',{
'CK': $('CK').val(),
'OCE': $('OCE').val(),
'range_04': $('range_04').val(),
},
function(data) {
var parsed = JSON.parse(data);
$("#xyz").text(parsed['abc']);
});
return false;
});
});
</script>
Now this code generates the json (and that json object feeds the model)
app = Flask(__name__)
api = Api(app)
parser = reqparse.RequestParser()
parser.add_argument('args.xyz')
class getPredProb(Resource):
def post(self):
args = parser.parse_args()
clf = joblib.load('AO.pkl')
frameToScore = pandas.read_json('args.xyz')
prediction = clf.predict(frameToScore)
probability = clf.predict_proba(frameToScore)
return json.dumps({'Prediction': prediction},{'Probability':probability}), 201, {'Access-Control-Allow-Origin': 'http://127.0.0.1:5001'}
api.add_resource(getPredProb, '/')
if __name__ == '__main__':
#http_server = WSGIServer(('', 5002), app)
#http_server.serve_forever()
app.run(debug=True,port=5002)
Image of ERROR
You're only passing a string, 'args.xyz', to read_json, you should use args['xyz'] (assuming this is the json data, as I don't see anything with an xyz key being passed to the backend).
class Survey(models.Model):
answers = models.ManyToMany('Answer')
class Answer(models.Model):
answer = models.CharField(max_length=30)
votes = models.IntegerField(default=0)
I want to display in html a list of answers and their vote count, and have for each of them a simple "+1" button that would increment the votes value.
If I had to reinvent the wheel, I would call a view upvote(answer_id) that would fetch the answer in the database, increment the votes and save, and also would do the same in javascript to update the corresponding field.
Is there a better way to do this in Django ?
Same question if in the html I allow the user to post a new answer.
All you have to do is a AJAX calls with a JSON response. Then update your DOM with javascript.
This is a dummy example:
Django side:
import json
def upvote(request, anwser_id):
# You shall write a better test
a = Answser.objects.get(pk=anwser_id)
a.votes += 1
a.save()
return HttpResponse(json.dumps({
'count': a.votes,
'pk': anwser_id
}, content_type="application/json")
Client site (assuming jQuery) - no need to post :
$.getJSON('/upvote/' + your_anwser_id + '/')
.done(function(data) {
// You've got your response here
$("YOUR-INPUT-SELECTOR").html(data.count);
})
.fail(function(xhr) {
// Server error
alert("Error: "+ xhr.responseText);
});
If someone votes between two queries, you'll have the correct anwser. I use that kind of solution for a like/dislike comments voting system.
I have a Client model, which includes a field for a client API key.
When adding a new client in Django Admin, I'd like to have a button next to the API field to generate a new key (i have the method for this). The field will then be updated with the key once generated.
How can I add this button next to the field? Should I use a custom widget?
In my case I am making an API call with a button I create so I'll throw in how I did that too. Ofcourse your button can do whatever you like.
First, in your model create a function that will output your button. I will use my example, i.e. models.py:
class YourModel(models.Model):
....
def admin_unit_details(self): # Button for admin to get to API
return format_html(u'<a href="#" onclick="return false;" class="button" '
u'id="id_admin_unit_selected">Unit Details</a>')
admin_unit_details.allow_tags = True
admin_unit_details.short_description = "Unit Details"
I then added the field as readonly and added it to the fieldsets, note you can only have either fields or fieldsets defined on the model admin. I aslo added media to overwrite some css and also added the js for where the ajax call will be made, admin.py:
class YourModelAdmin(admin.ModelAdmin):
form = YourModelForm
list_display = ('id', 'agent', 'project', 'completed_date', 'selected_unit', 'is_accepted',
'get_lock_for_admin', 'status')
fields = ('agent', 'project', 'completed_date', 'selected_unit', 'is_accepted',
'lock', 'status')
readonly_fields = ('admin_unit_details', )
...
class Media:
js = ('admin_custom/js/myjs.js',) # in static
css = {'all': ('admin_custom/css/mycss.css', )}
I also wanted to note that I passed the API address and header through the Form, but you can use the right header/password in the code. I just keep mine all in one place (settings.py), forms.py (optional):
from settings import API_ADDRESS, API_HEADER
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(WorksheetForm, self).__init__(*args, **kwargs)
self.fields['selected_unit'].widget = forms.Select(choices=get_worksheet_unit_choice_list(self.instance.id),
attrs={'api_address': API_ADDRESS, 'api_header': API_HEADER})
....
Lastly here is a look at my js, as referenced by my admin Media class, it is in admin_custom/js/myjs.js:
This is similar to adding an admin image, see here. Also search for allow_tags attribute in this django doc, it shows a good example.
// Make sure jQuery (django admin) is available, use admin jQuery instance
if (typeof jQuery === 'undefined') {
var jQuery = django.jQuery;
}
var unit_information = {};
jQuery( document ).ready(function() {
jQuery('#id_admin_unit_selected').click( function() {
//get the data from id_selected_unit, from the api_header api_address attributes
var unit_select = jQuery('#id_selected_unit');
var header = unit_select.attr('api_header');
var address = unit_select.attr('api_address');
var selected_unit = unit_select.val();
if (header && address && selected_unit){
var unit_address = address + '/units/' + selected_unit
get_unit(header, unit_address)
}
else{
// if can't connect to api, so hide
jQuery('.field-admin_unit_details').hide();
}
});
});
function get_unit(header, address){
jQuery.ajax
({
type: "GET",
url: address,
dataType: 'json',
headers: {
"Authorization": header
},
success: function (result) {
//TODO: output this in a modal & style
unit_information = JSON.stringify(result);
alert(unit_information)
},
error: function(xhr, textStatus, errorThrown) {
alert("Please report this error: "+errorThrown+xhr.status+xhr.responseText);
}
});
}
This outputs it in an alert, you can also log it to the console or define your own modal / style for it.
Hope this helps, Cheers!
I don't know if this is even possible, any way, I currently have something as the following:
class Incidence(models.Model):
...
instalation = models.ForeignKey('Instalation')
machine = models.ManyToManyField('Machine')
...
class Machine(models.Model):
...
instalation = models.ForeignKey('Instalation')
...
So Machines belongs to instalations and incidences are related to machines and incidences, the idea is to put a dynamic FilteredSelectMultiple widget to select the machines related with the incidence in the admin page. The admin currently is something as:
class IncidenceMachineForm(forms.ModelForm):
filtered_machine = ModelMultipleChoiceField(
queryset=Machine.objects.order_by('hostname'),
required=False, widget=FilteredSelectMultiple("filtered machine name", is_stacked=False)
)
class Meta:
model = Incidence
And then, the modelAdmin uses the form IncidenceMachineForm. The idea is that when you select the instalation of the incidence, only the machines related to that instalation are available for selection. I guess something as this is not possible:
queryset=Machine.objects.filter(instalation=self.instalation).order_by('hostname'),
Any ideas will be highly appreciated. Thanks!
I notice that FilteredSelectMultiple widget has already cached, converted and changed the name of original widget after the page is loaded, so changing the "option" list of "select" tag is not enough.
I came up with this solution:
wrap "select" list inside another element ("div" for instance)
use data received from ajax call to re-create the original list
call "SelectFilter.init" to re-construct the FilteredSelectMultiple widget
Here is the code I have tested:
$('#id_instalation').change(function() {
var selected = $('#id_instalation').val();
if(selected) {
$.ajax({
url: '/url/to/get/machines/' + selected,
success: function(list) {
var options = [];
options.push('<select multiple="multiple" class="selectfilter" name="machine" id="id_machine">');
for(i in list){
options.push('<option value="' + list[i][0] + '">' +
list[i][1] + '</option>');
}
options.push('</select>');
$('#machine_wrapper').html(options.join(''));
// Change title of widget
var title = $('#id_instalation option:selected"').text().toLowerCase();
SelectFilter.init("id_machine", title, 0, "/path/to/django/media/");
},
error: function() {
alert('Server error');
},
});
}
}
This is the sample of data returned from ajax call:
[[1, "Machine 1"], [2, "Machine 2"], [3, "Machine 3"]]
For server side implementation, please see Chris Pratt's answer
Note: tested with:
jquery-1.7.2
django 1.2.5
You can do that after the model has been saved, and there's an instalation associated with it to use (though the lookup would be instalation=self.instance.instalation).
However, that doesn't do you much good, because if a different instalation is selected the list would still be the one for the old selection, and obviously you get no help when first creating the object.
As a result, the only way to accomplish this is with AJAX. You create a view to receive the selected instalation id, and return a JSON response consisting of machines associated with it. Tie the view into your urlconf, and then hit it with AJAX and update the select box based on the results.
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils import simplejson
def ajax_admin_get_machines_for_instalation(request):
instalation_id = request.GET.get('instalation_id')
if instalation_id is None:
# instalation_id wasn't provided so return all machines
machines_qs = Machine.objects.all()
else:
instalation = get_object_or_404(Instalation, pk=instalation_id)
machines_qs = Machine.objects.filter(instalation=instalation)
# 'name' is the field you want to use for the display value
machines = machines_qs.values('pk', 'name')
return HttpResponse(simplejson.dumps(machines), mimetype='application/json')
Then the JS:
(function($){
$(document).ready(function(){
function update_machine_options(){
var selected = $('#id_instalation').val();
if (selected) {
$.getJSON('/url/for/ajax/view/', {
instalation_id: selected
}, function(data, jqXHR){
var options = [];
for (k in data) {
options.append('<option value="'+data[k].pk+'">'+data[k].name+'</option>');
}
$('#id_machine').html(options.join(''));
});
}
}
update_machine_options();
$('#id_instalation').change(function(){
update_machine_options();
});
});
})(django.jQuery);
from django.contrib.admin.widgets import FilteredSelectMultiple
#admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
kwargs['widget'] = FilteredSelectMultiple(
db_field.verbose_name,
False,
)
return super().formfield_for_manytomany(db_field, request, **kwargs)
fast and don't need to override ModelForm or etc.
effect all m2m fields.