This question is directed to anyone with both flask (python) and sailsjs knowledge. I am very new to the concept of web frameworks and such. I started using flask but now I must use Sailsjs. In flask, I can define a route as:
#app.route('/company/<org_name>')
def myfunction(org_name):
...use org_name to filter my database and get data for that company...
return render_template('companies.html', org_name=org_name, mydata=mydata)
Where I can use myfunction() to render a template in which I can pass the parameters org_name and mydata.
In sails, I am confused as to how to define my route with a given parameter. I understand:
'/': {
view: 'companies'
}
but I am not sure how to make the route dynamic in order to accept any variable org_name.
Another problem is that in python, mydata is a query from a MySQL database. I have the same data base connected to Sailsjs with the model completely set up but I am sure this model is useless. The site that I am creating will not be producing any new data (i.e. I will neither be updating nor saving new data to the database).
My Question is thus: with
'/company/:org_name': {
view: 'companies'
}
where should I create the function that filters the database? how should I be sure that sails will pass that org_name parameter into the function and how should I pass the data as a parameter into an html template?
Thanks a ton.
There are 2 options here but it helps to explain a bit about each in order for you to pick the best course of action.
Firstly, routes...You can indeed as you have shown render a view from a route directly, but you can also do a few other things. Below are 2 snippets from the sails.js website docs:
module.exports.routes = {
'get /signup': { view: 'conversion/signup' },
'post /signup': 'AuthController.processSignup',
'get /login': { view: 'portal/login' },
'post /login': 'AuthController.processLogin',
'/logout': 'AuthController.logout',
'get /me': 'UserController.profile'
}
'get /privacy': {
view: 'users/privacy',
locals: {
layout: 'users'
}
},
Snippet 1 shows how you can render a view directly as you have shown but also how you can point to a controller in order to do some more complex logic.
The login within your controller could mean that for the /me "GET" route you can execute a database query within the "profile" method to accept a get parameter, find a user and then display a view with the users data within. An example of that would be:
Profile: function (req,res){
User.find({name: req.param('name')}.exec(function founduser(err,result){
return view('profile',{userdata: result});
});
}
In the second snipped from the sails docs you can see "locals" being mentioned. Here in the GET privacy route we see that the view is being told whether to use the layout template or not. However, with that being said there is nothing stopping you pushing more into the locals such as users name etc.
In my opinion and what I feel is best practice, I would leave your routes.js to be quite thin and logicless, put the database queries/logic/redirections in to your controller.
For your specific example:
My routes.js file may look like this
// config/routes.js
module.exports.routes = {
'get /companies': 'CompanyController.index',
'get /companies/:orgname': 'CompanyController.company'
}
This allows the first route to potentially show a list of companies by going to /companies and my second route may fire when a get request is made based on clicking a company name e.g. /companies/Siemens
My CompanyController.js for these may look like this:
module.exports = {
index: function (req, res) {
return res.view('companieslist');
},
company: function (req, res) {
var companyName = req.param('orgname'); //Get the company name supplied
//My company model which could be in api/models as Company.js is used to find the company
Company.find({name: companyName}).limit(1).exec(function companyresult(err,result){
//Error catch
if(err){return res.negotiate(err)};
//The result of our query is pushed into the view as a local
return res.view('company',{companydata: result}); //Within the .find callback to ensure we keep async
});
}
};
In my view I can access the data retrieved under "companydata" e.g. for EJS:
<%=companydata[0].name%>
If you need any further help/clarifications let me know. I do recommend taking a look at the sails.js documentation but if you really want to get your head around things I recommend sails.js in Action which is an ebook from mannings. A couple of days of reading really got me up to speed!
Related
I have a general question to the django-admin.
Is it possible to react on form changes?
I have a select field in my django-admin detail site. Whenever I change the data from the select field, I want to change fields which are read-only.
Has anybody ever dealt with this issue?
My two cents:
As any other guys said it is a javascript work. In admin pages Django pases jquery. It is called django.jQuery. So basilly you would do what #Ashfaq suggested. You will create a custom_script.js and added to the Media metaclass.
Basically(as #Ashfaq):
class MyModelAdmin(admin.ModelAdmin):
class Media:
js = ("js/custom_script.js",)
and custom_script.js will be something like this(assuming that your select field is called id_category):
django.jQuery( document ).ready(function() {
console.log( "ready!" );
django.jQuery('#id_category').change(function() {
alert( "Handler for #id_category was called." );
});
});
The ready function will guarantee the handler is getting set.
I think the thing that will work here is to add jQuery + your custom javascript and play with the events / clicks what-ever you want with elements.
class MyModelAdmin(admin.ModelAdmin):
class Media:
js = ("js/custom_script.js",)
in custom_script you can add click or change events as you want.
Found a great answer by Abhijith K in another SO Q&A:
How can i add a onchange js event to Select widget in Django?
reciept=forms.ChoiceField(reciept_types, widget = forms.Select(attrs = {'onchange': "myFunction();"}))
To be clear, this is what you add within your widget definition: attrs = {'onchange': "myFunction();"} to define which JS function will be called, in this when an onchange event is triggers.
In the ModelAdmin you can then define a JavaScript file that you want to have access to, where you can define your function "myFunction()":
#admin.register(AnswerTree)
class AnswerTreeAdmin(ModelAdmin):
form = AnswerTreeForm
...
class Media:
js = ("admin_js/answertree_modeladmin.js",)
Django docs on defining assets (Like JavaScript & CSS) on your ModelAdmin:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-asset-definitions
What is also useful, I found out that you can access the value of the input by using attrs = {'onchange': "myFunction(this.value);"}, note that I am passing the argument this.value here now.
Example of the JavaScript function definition:
myFunction = (value) => {
console.log(value)
}
OR
myFunction(value) {
console.log(value)
}
I was wondering if someone could help me.
I want to be able to click on customer and locations be based off of the certain customer, being a dependent dropdown. This information is coming from a database, hence the queries in the following code.
This is my form function for both customer and location
class CustomerPick(SubForm):
customer = QuerySelectField(u'Customer',
get_label=u'sCustomer',
query_factory=lambda :
(TCustomer.query.order_by(TCustomer.sCustomer)),
validators=[DataRequired(),])
location = QuerySelectField(u'Location',
get_label=u'sLocation',
query_factory=lambda :
(TLocation.query.order_by(TLocation.sLocation)),
validators=[DataRequired(),])
Here is the view portion
#route('new/', methods=['GET', 'POST'])
def new(self):
form = CustomerPick()
if form.validate_on_submit():
This is a picture of the dropdown also for reference, if there is anything else needed for you guys to have a go please let me know. Thanks in advance!
Photo
I don't quite get your question but you want to be able to click a user and populate the dropdown based on the location?
This involves some Ajax sending data back and forth.
I'll give you a minimized version of code snippet (not tested).
// front-end - this handles user behavior in dropdown. When a user changes a value
// in a user drop down, it will send a request to your Flask view function.
$("#user_drop_down").change(function () {
let user_identifier = this.value;
$.ajax({
type: "GET",
url:/url_to_flask_view_function/,
data: {user_identifier: user_identifier},
success: function (resp) {
$('#your_designated_div_for_both_dropdowns_div').html(resp.data)
}
});
});
# back-end - this receives the request sent from front-end and process the Flask-WTF
# form options so that it can render different options in the dropdowns. In this view
# function, we will return jsonified template with newly processed form_object
# instead of rendering the option data. You can return the json with only the data
# but it involves more javascript parsing and may be difficult for you.
#app.route('/url_to_flask_view_function/')
def form_processing():
user_identifier = request.args.get('user_identifier)
# now we've gotten the user_identifier, you will need to query. Below query is totally made up.
query_to_where_location = Location.query.filter(Location.user_identifier= user_identifier).first()
# your query result will not be in a tuple format
# if your query result is like this "locA, locB, locC, locD", you need to process
# it so that you make [('locA', 'locA'), ('locB', 'locB').......]
form_object = MyForm()
form_object.location.choices = processed_list
return jsonify({"data":render_template('template_that_contains_your_drodpdowns.html',
form_obj=form_obj)})
<!-- HTML piece, you should've had this already but make sure to specify your fields in HTML following Jinja2 synthax.-->
<form>
{{form_object.user}}
{{form_object.dropdown}}
</form>
In conclusion, the idea here is that you catch user behavior using .change, then based on the change, you will send request with user_identifier to server side. Once it reaches server-side, you will make a query into the DB and render the same template again with differently processed forms.
The best way to go about doing this is that, once you get the user_identifier into your view, you make query and return jsonified location object, then in your success block, you would alter the of that dropdown input element.
Let me know if you have more questions.
I'm trying to add some autocomplete field in Django Admin (Django 2.0.1).
I managed to get it to work (both with the autocomplete included in Django and with Select2) because in those cases I loaded the dropdown options from a ForeignKey field.
Now I need to have autocomplete on a simple CharField but the choices need to be taken from a remote API that return a json response. I can decide how to structure the json response. Any way to do this?
The returned json responde doesn't represent objects of model, just simple text options.
Not sure if this fits your needs, but here was a solution to a similar problem (remote API, JSON, autocomplete text input). Select portions of the code:
HTML
<label>Which student? (search by last name.)</label>
<input type="text" name="studentname" id="student_name">
JS
// Build list of all students - hit API.
var ajax = new XMLHttpRequest();
ajax.open("GET", "example.com/api/student/?format=json", true);
ajax.onload = function() {
students = JSON.parse(ajax.responseText);
list = students.map(function(i) {
name = i.last_name + ', ' + i.first_name;
return name;
});
var input = document.getElementById("student_name");
new Awesomplete(input, { list: list });
};
ajax.send();
All this of course requires the Awesomplete JS library.
This is a solution when working outside the Django admin, but I think could be adapted to work within the admin setting without too much difficulty?
Perhaps something like this in your ModelAdmin?
def special_field(self, obj):
return render_to_string('special.html')
special_field.allow_tags = True
Then throw the aforementioned HTML/JS in special.html.
Finally you'll need to remove the old field from your ModelAdmin, add your new custom field, and likely override your ModelForm - something like this:
def save(self, commit=True):
extra_input = self.cleaned_data.get('studentname', None)
self.instance.full_name = extra_input # the model instance to save to
return super(NameOfYourForm, self).save(commit=commit)
I am working on a django project in which I have to design a sales order page for order processing,now as per requirement I have to create multiple information forms on single page with different fields (formset does not helpful in this case) which I achieved by creating specific form class and then render them in template through class view function
def get_context_data(self, **kwargs):
context = super(CreateOrderView, self).get_context_data(**kwargs)
context['ord_info_form'] = OrderInformationForm()
context['billing_form'] = BillingInformationForm(prefix='billing')
context['shipping_form'] = ShipingInformationForm(prefix='shipping')
context['payment_form'] = PaymentInfoForm()
context['summary_form'] = OrderProductsForm()
return context
My template look like this:
Now, I have to save data for all forms which belongs to multiple models like billing, contact and shipping information. So for save information I created another form class for create order and set it in form_class variable in Createview class, and I override the save function in form class in which I manipulate the data for all model forms. For update order (put operation) I did the same I create another form class for update like create one in form class.
As per my understanding I implement this strategy, but this seems to me repetitive design. How can I design it better for multiple forms with different fields. Please let me know what kind of design pattern I can apply for this.
P.S: If require I'll post more code and assets for clear understanding.
Thanks in advance.
I've been working on exactly the same requirement. What we have done is the following:
Create the classbasedview to render the forms and add them to the context just as you did on the top.
I see you've created the template based on the forms you added to the context, but as someone else pointed out you need to use an alternative rather than just django to make the post of each item. In my case I used plain old ajax to perform my post. So it looks something like this:
function firePost() {
var endpoint = '/sales_center/orders/add';
var channel = getChannelForm();
var order = getOrderForm()
var ip = getIPForms();
var collections = getCollections();
var subscriptions = getBookSubs();
var perpetual = getBookPerp();
$.ajax({
type: 'POST',
url: endpoint,
data: {
'order':JSON.stringify(order),
'channel': JSON.stringify(channel),
'ips':JSON.stringify(ip),
'collections': JSON.stringify(collections),
'subscriptions': JSON.stringify(subscriptions),
'perpetual': JSON.stringify(perpetual)
},
success: function(data) {
console.log(data);
},
error: function(data) {
console.log(data);
}
});
}
In the data section I divided the information by name. My order is composed of a channel, multiple collections, multiple ips, multiple subscriptions and multiple perpetual purchases. The endpoint is the same as the one in the page so in the backend in order to process that we do it like this:
#transaction.atomic
def post(self, request):
channel = self._create_channel(json.loads(request.POST.get('channel')))
order = self._create_order(json.loads(request.POST.get('order')), canal)
ips = self._assign_ips(json.loads(request.POST.get('ips')), canal)
return JsonResponse({}, status=200)
The code above is just an example the _create_channel is a function in our classbasedview. Each function performs the posts in our backend for each model. That way it looks clean and easy to read each step of the order.
I added the #transaction.atomic to perform
any rollbacks in case in one critical step of the order we have
inconsistent data and we need to rollback or delete the data saved before in the database.
In each function we then use the forms we sent on the template in the context data, we use them to save or get errors in case something isn't filled out correctly. We do something like this:
def _create_channel(self, data):
form = ChannelForm(data)
if form.is_valid():
new_channel = form.save(commit=False)
new_channel.save()
return new_channel
else:
return JsonResponse(form.errors, status=400)
I hope the example above helps you.
My suggestion would be:
Build a stable(take your time here, you don't want any redundant data) database using django models.
Use Django Rest Framework to build proper serializers and validators against every model.
Route them to urls and perform other linking operations on your views.
Choose a javascript framework, you are comfortable with(Vue.js,Angular2,React which ever you want) and write the frontend separately(this part will have your multiple forms and stuff).
Use axios to post your data in django.
This is my personal suggestion, you can also go for django formsets but that will not be suitable in your example.
Is it possible to call Django Manager Methods from AngularJS?
For example like this factory PUT method called in AngularJS:
var app = angular.module('app', ['ngResource', 'ngRoute']);
// Some APIs expect a PUT request in the format URL/object/ID
// Here we are creating an 'update' method
app.factory('Notes', ['$resource', function($resource) {
return $resource('/notes/:id', null,
{
'update': { method:'PUT' }
});
}]);
// snipped ...
Notes.update({ id:$id }, note);
But for Django's get_or_create for example?
So instead of this in Django:
obj, created = MyObject.objects.get_or_create(**kwargs)
I'd like to do something like:
app.factory('Notes', ['$resource', function($resource) {
return $resource('/notes/:id', null,
{
'get_or_create': // some code here but do not know what??
});
}]);
// snipped ...
Notes.get_or_update({ id:$id }, note);
I'd like to do this because I have written a good deal of custom Django Managers and would like to use AngularJS for the UI, but don't want to re-write them all if necessary.
Thanks
The point is that Angular is a client-side framework, and Django is server side. The only way they can communicate is via HTTP, which means that if you want Angular to call a Django manager method, you need to expose that via a URL connected to a view. As the comments say, one way to do that is via Django REST framework, but if that's overkill for your use case you can always write the views yourself.