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)
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 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.
I need django modelform with 2 fields, where second field choice list depends on what was chosen in first one. My model:
class Offer(BaseModel):
VEHICLE_TYPES = (
('personal','Personal car'),
('truck','Truck'),
)
vehicle_type = models.CharField(max_length=32, choices=VEHICLE_TYPES, default='personal', verbose_name='Vehicle type')
PERSONAL_MAKES = (
('',''),
)
TRUCK_MAKES = (
('',''),
)
make = models.CharField(max_length=32)#what more??
How can I set choices of make field to PERSONAL_MAKES if vehicle_type is set to personal? How can I do this? Is it possible on model level?
You probably can't because it depends of user interaction with your form: your server can't know in advance which element your user will select before sending the form to the browser. You could probably achieve this using ajax. I think a working process could be :
Create a form with all the fields, and make make field hidden
Create a view (I'll call it AjaxMakeFieldView) that will catch an ajax request taking a vehicle_type argument and return the HTML for make field, populated with relevant data. Add a URL in your URLConf for this view.
In your template, add a Javascript binding : when user select a vehicle_type, the browser will send aan ajax request to AjaxMakeFieldView and replace hidden make field with returned HTML
If you don't want javascript, another way would be a two step form :
A first form with a vehicle_type field
Once the first form is submitted, your user get a second form with a make field, which initial data is populated depending of vehicle_type selected in the first form.
I've never done this, but Django documentation on Form wizard seems a good place to start.
This is how I ended up having two model choice fields depending on each other, on one page. In the below, field2 is depending on field1:
Javascript portion
Note that in $.each(), $.parseJSON(resp) should NOT be used (instead of just json) as we're already have it parsed by jQuery (due to the content_type='application/json' response) - see I keep getting "Uncaught SyntaxError: Unexpected token o".
$(document).ready(function() {
$("#id_field2").empty();
$("#id_field1").change(function(){
$.ajax({
url: "{% url 'ajax_get_field_2' %}",
type: 'GET',
data: {field1_id: $("#id_field1").val()},
dataType: "json",
success: function(resp){
$("#id_field2").empty();
$.each(resp, function(idx, obj) {
$('#id_field2').append($('<option></option>').attr('value', obj.pk).text(obj.fields.code + ' (' + obj.fields.affection + ')'));
});
},
error: function(jqXHR, textStatus, errorThrown) {
alert(errorThrown);
}
});
});
});
Django views.py portion
Note that this can probably be done by django-rest-framework as well.
I'm obtaining fields=('id', 'code', 'affection')) from my MyModel2 - these can then be reached in JQuery using obj.fielsd.<myfieldname>.
class AjaxField2View(generic.View):
def get(self, request, *args, **kwargs):
field_1 = get_object_or_404(MyModel1, pk=request.GET.get('field1_id', ''))
model2_results = MyModel2.objects.filter(k_value=field_1 .k_value)
return HttpResponse(serializers.serialize('json', model2_results, fields=('id', 'code', 'affection')), content_type='application/json')
Another smart method query/free and no views needed,
is to make html tags for options that related to field A choice.
For example, in field B options:
<option example_tag="example_value" value="5"></option>
This tag should be related to either field A value or text.
Using JS to display block/none using this option tag accordingly.
And don't forget to reset value for every time user changes field A.
I have a series of filters I need to run and then I want the ability to loop though the results. I'm thinking of starting with a form where I can select the filter options. I want a next/previous buttons when I'm looping.
How would I implement this? I'm just looking for high level advice and sample code if available.
I know I can set index_template in AdminSite to create the first page. I know there is the SimpleListFilter but I don't think I can use it since I want multiple filters that need to be configured. Also I don't want to have to select all the models to loop though them. I plan on writing a custom add/change view.
I'm not sure how to go from the selected filter options to looping though each of the selected models. I'm not sure if I can pass and store a query set for when I loop though each model. Some of the options I've thought about is storing the filter parameters in the url and the current model number. Another thing I thought about is storing the results in database and recalling it.
Update
Someone thought this was too broad so I'll be a little more specific. I think the best solution will be to inherit from AdminSite and overwrite index_template to be a form that will contain the filters. How would I link the form submit to a view that will loop though the items? I assume I'll need to add a custom view to the admin but I'm not sure how to pass the data to the view.
This is quite a broad question but I'll give it a shot.
There are a few ways you can achieve this:
Setting up a model with filter queries as variables.
models:
class Filter(models.Model):
Filter_Query = models.CharField(max_length=30)
views:
from app_name.models import Filter, Some_Model
def filter(request, pk):
template = loader.get_template("app_name/filter_search.html")
filter_1 = Filter.objects.get(id=pk)
some_model = Some_Model.objects.all()
filter_1_search = model_name.filter(some_option=filter_1)
context = RequestContext(request, {'filter_1_search': filter_1_search})
return HttpResponse(template.render(context))
Then in a separate page, you can load the results like so.
{$("#some_div").load(filter/1)
or even easier you can you can just use AJAX to send whatever filter query you want.
views:
from app_name.models import Some_Model
def filter_query(request):
filter_1 = request.GET.get('filter_query', '')# Receives from AJAX
some_model = Some_Model.objects.all()
filter_1_search = model_name.filter(some_option=filter_1)
jsonDump = json.dumps(str(filter_1_search))
return HttpResponse(jsonDump, content_type='application/json')
javascript:
var data_JSON_Request = {'filter_query': filter_search1, 'csrfmiddlewaretoken': "{{csrf_token}}"};//JSON package.
function ajax_call(data_JSON_Request){
$(function jQuery_AJAX(){
$.ajax({
type: 'GET',
url: '/filter_query/',
data: data_JSON_Request,
datatype: "json",
success: function(data) {$("#sove_div").load(data);
open_model_menu();
},//success
error: function() {alert("failed...");}
});//.ajax
});//jQuery_AJAX
};//ajax_call