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.
Related
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)
Is it possible to validate a WTForm field after leaving the field but before submit?
For example, after entering a username, that field is validated to see if its available and shows a checkmark, before the user clicks submit.
When the field is changed, perform a check and change the text in an adjacent node. Some things can be validated directly in the browser. To validate against data on the server, send a request with JavaScript to a view that checks the data and returns a JSON response.
#app.route('/username-exists', methods=['POST'])
def username_exists():
username = request.form['username']
exists = check_if_user_exists(username)
return jsonify(exists=exists)
<input id='username' name='username'>
<p id='username-status'></p>
var username_input = $('#username');
var username_status = $('#username-status');
$('#username').on('focusout', function () {
$.post(
"{{ url_for('username_exists') }}",
{
username: username_input.val()
},
function (data) {
username_status.text(data.exists ? '✔️' : '🙅');
}
);
});
This example uses jQuery, but the concept is not specific to any library.
Alternatively, post the entire form to a separate view that only validates the fields, then return jsonify(form.errors) and do something with them in the browser. The code would be essentially the same as above, with some extra logic to put the error messages next to the correct fields.
Remember to still validate the data when the form is submitted, as requests can be made outside the browser with other
I have view in django that add product to the cart( i use django-carton 1.2). That my code:
def add(request,product_id):
cart = Cart(request.session)
product = Product.objects.get(pk=product_id)
if product.quantity >=1:
cart.add(product, price=product.price)
product.quantity-=1
product.save()
return render (request,'shopping/show-cart.html')
else:
return HttpResponse("No product ")
After that view has worked a certain product add to the cart, cart with all products is showing. The problem: when in browser I make function "reload current page" it increase quantity of products in my cart. At the same my remove product view is working, but it only try delete the product when page reload from the function of browser
You should only do actions that modify data - like add and delete - on a POST request, not a GET. You need to create a form in your page, even if it just contains a single button, and check if request.method == 'POST' in the view before doing anything.
Either include a form tag within your html that POSTS information or you can use an Ajax request call.
<script type="text/javascript">
$(document).ready(function(){
$('#add_cart_button').click(function(e)
{
$.ajax({
url: 'xxxxxxx',
type: 'POST',
dataType: 'html',
data: {
'somevariable' : 'somevalue'
},
async: false,
success: function(data){
$('#target').html(data);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert("Status: " + textStatus); alert("Error: " + errorThrown);
}
});
When you make the Ajax call, it sends whatever you have in your data dictionary to the specified url. From there, Django implements whatever function you want to process that data on the backend and returns the information back. The success function basically gets that information back and does whatever you want with it. In your case, you probably just want to re-render that chunk of HTML that displays the items in your cart.The target tag within the success function is where that chunk of HTML will be rendered, so include a target div tag in your html where you want it to be displayed.
You can access the data from the ajax request in your view by doing request.POST.get('key',somedefaultvalue) (if you want to have a default value if it can't find the dictionary or if it's empty) or just request.POST[key].
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
I have some question:
I use django form, and fields like MultipleChoiceField
in view.py I clean data and get GET URL like this
http://localhost:8000/?category=&style=&sex=&brand=ASICS&brand=Be+Positive&low_price=&high_price=
Give me advise, can I regroup brand field and hide empty.
I want getting something like this:
http://localhost:8000/?brand=1+2
And else one question:
How can I set empty value(empty_label) for forms.ModelMultipleChoiceFIeld
forms.py:
brand = forms.MultipleChoiceField(required=False,
widget=forms.SelectMultiple(attrs={'size':1})
)
def __init__(self,app_label=None, *args, **kwargs):
super(Search, self).__init__(*args, **kwargs)
self.fields['brand'].choices = [('', 'All brands')]+[(brand.name, brand) for brand in Brand.objects.all() ]
views.py:
if request.method == 'GET' and request.GET:
form = SearchForm(app_label, request.GET)
if form.is_valid():
brands = form.cleaned_data['brand']
kwargs.update({"brand__name__in": brands})
This is how the browser submits multiple data. It's part of the HTML specification, trying to change it would be folly and technically I can't understand why you would try to care about how your url GET data looks.
That being said, if you want to change the way it submits you'll need javascript to transform the data on form submit. Django has nothing to do with the matter.
Using jQuery for example:
$('#form').submit(function(){
//Get form data
//Transform into my custom set of vars
//Redirect to form's ACTION with my querystring appended.
});
Please keep in mind you will not get any automatic parsing of the values on the Django side. Normally it would turn it into a list for you, but now you're responsible for parsing the 'value+value+value' yourself.
For empty label in forms you could do this -
class SomeForm(forms.Form):
h=forms.CharField(label=u'',widget=forms.TextInput(attrs={'value':'Search'}))
By keeping label as '', you get the label as empty. The attrs are basically the HTML attributes of the form text field.
UPDATE: I didn't understand the first part of your Q, elaborate...