Call js function every time DjangoForm is loaded? - django

I have next form:
class ExampleForm(forms.ModelForm):
class Meta:
model = ExampleModel
fields = ['field1', 'field2', 'field3']
widgets = {'field1': forms.Select(attrs={'onchange': 'onchangeJsFunction()'})}
So far i have been able to add onchange property to my select field that calls 'onchangeJsFunction()' every time 'field1' value is changed. My next goal is to add a function that is going to be executed every time form is loaded, and i am not sure how to do it. I have tried adding onload property to a input field this way:
...
widgets = {'field_2': forms.TextInput(attrs={'onload': 'onloadJsFunction();'})}
...
but it does not give any results so far.
How am i supposed to execute certain JS function every time my Django Form is loaded?

Based on the comment that you are using a bootstrap modal - you could add an event listener to the modal that runs code when it is is shown
In bootstrap 4 (jquery)
$('#myModal').on('shown.bs.modal', function () {
//onloadJsFunction() or similar
})
in bootstrap 5
var myModal = document.getElementById('myModal')
myModal.addEventListener('shown.bs.modal', function () {
////onloadJsFunction() or similar
})
(tweaked from bootstrap docs)

Related

Populate Django Form field-labels from a database model when calling this Form thru a sidebar menu item

I am new to Python, Django and web programming, so may be this is a dumb question or conceptually a wrong logic. I am trying to call an entry Form from sidebar menu item and want this Form fields to have labels from a database model, so that same form/database model can be used for different purposes. The sidebar menu is populated using Django-MPTT. This form pop ups normally with it's original labels when clicking the menu item, but if I try to set it's field labels from database model with relative labels, it gives error: 'QuerySet' object has no attribute 'task_t3'. May be it is due "GET" request initiated on menu item click. Any solution to achieve this? Following is code lines from views.py where this error occur:
ctlobj = TaskControl.objects.filter(task_group_no=5)
taskdetail_form = TaskDetailForm()
taskdetail_form.task_3.label = ctlobj.task_t3
The problem was resolved by looping over variable ctlobj and then assigning values to field label in required form:
ctlobj = TaskControl.objects.filter(task_group_no=5)
taskdetail_form = TaskDetailForm()
for obj in ctlobj:
taskdetail_form.task_3.label = obj.task_t3

Django Admin intercept onchange event of a field and make an action

In my django project i would clear a field value every time another select field have an onChange event.
I have an add form like thisone:
every time Template field change (onChange), Test Case field have to become blank.
How can i do this in a django admin add or edit page?
So many thanks in advance
You could customize Admin asset definition and use JavaScript/jQuery to handle your problem. Here is an example:
admin.py
class TestCaseAdmin(admin.ModelAdmin):
class Media:
js = (
'js/admin.js', # inside app static folder
)
admin.site.register(TestCase, TestCaseAdmin)
js/admin.js
if (!$) {
// Need this line because Django also provided jQuery and namespaced as django.jQuery
$ = django.jQuery;
}
$(document).ready(function() {
$("select[name='template']").change(function() {
$("select['test_case']").val('');
});
});
template, test_case are field name on your model
The event can also be set in the modelform meta widgets dict;
class ReceiptsForm(ModelForm):
class Meta:
model = Receipts
fields = []
widgets = {
'partner_id': forms.Select(attrs={'onchange': 'this.form.submit();'})
}

How to render django form differently based on what user selects?

I have a model and a form like this:
class MyModel(models.Model):
param = models.CharField()
param1 = models.CharField()
param2 = models.CharField()
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('param', 'param1', 'param2')
Then I have one drop down menu with different values and based on what value is selected I'm hiding and showing fields of MyForm. Now I have to take one step further and render param2 as a CheckboxInput widget if user selects a certain value from a drop down but in other cases it should be standard text field. So how would I do that?
I know this post is almost a year old, but it took me multiple hours to even find a post related to this topic (this is the only one I found, which came up as related when submitting my own question), so I felt the need to share my solution.
I wanted to have a form that would show and require a text field if an option from a dropdown menu matched a value stored in another model. I had a foreignKey relation between two models and I passed an instance of Model1 into the ModelForm for Model2. If a value chosen for a variable in Model2 matched a variable already set in Model1, I wanted to show and require a textfield. It was basically a "choose Other and then enter your own description" scenario.
I did not want the page to reload (I was trying to have this work in both mobile and desktop browsers with the least delay/reloads and using the same code for both), so I could not use the mentioned multiple forms loading in a view option. I started trying to do it with AJAX as suggested above when I realized I was over thinking the problem.
The answer was using JS and clean methods in the form. I added a non-required field (field1) that was not in Model2 to my Model2Form. I then hid this using jQuery and only displayed it (using jQuery) if the value of another field (field2) matched the value of the variable from Model1. To make that work, I did decide to have a hidden < span > in my template with the pk of the variable so I could easily grab it with jQuery. This jQuery worked perfectly for hiding and showing the field correctly so the user could choose the "other" value and then decided to choose a different one instead (and go back and forth endlessly).
I then used a clean method in my Model2Form for field1 that raised a ValidationError if no value was entered when the value in field2 matched my Model1 variable. I accessed that variable by using "self.other = Model1.variable" in my __ init __ method and then referencing that in the clean_field1 method.
I would have liked to have been able to accomplish this without having to hide and show a field with JS, but I think the only solutions for doing so with views or ajax caused delays/reloads that I did not want. Also, I liked the general simplicity of the method I used, rather than having to figure out how to pass partial forms back and forth through the HTTPRequest.
Update:
In my situation, I was creating entries for lost and found items and if the location where the item was found was not a provided option, then I wanted to show a textbox for the user to enter the location. I created a location object that was set as the "other" location and then displayed the textbox when that object was selected as the "found" location.
In forms.py, I added an extra CharField and use a clean method to check if the field is required and then throw a ValidationError if it wasn't filled in:
class Model2Form(forms.ModelForm):
def __init__(self, Model1, *args, **kwargs):
self.other = Model1.otherLocation
super(Model2Form, self).__init__(*args, **kwargs)
...
otherLocation = forms.CharField(
label="Location Description",
max_length=255,
required=False
)
def clean_otherLocation(self):
if self.cleaned_data['locationFound'] == self.other and not self.cleaned_data['otherLocation']:
raise ValidationError("Must describe the location.")
return self.cleaned_data['otherLocation']
Then in my JavaScript, I checked if the value of the "found" location was the "other" location (the value of which I had in a hidden span on my html page). I then used .show() and .hide() on the textbox's parent element as necessary:
$("#id_locationFound").change( function(){
if ($("#id_locationFound").val() == $("#otherLocation").attr("value")){ //if matches "other" location, display textbox; otherwise, hide textbox
$("#id_otherLocation").parent().show();
}else
$("#id_otherLocation").parent().hide();
});
Your best guess would be to trigger a "POST" request when you select something from your drop down menu.
The Value of that "POST" has to correspond your values you use to determine which field you would like to output.
Now you will actually need two forms:
class MyBaseForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('param', 'param1', 'param2')
class MyDropDownForm(MyBaseForm):
class Meta:
widgets = {
'param2': Select(attrs={...}),
}
So as you can see the DropDownForm has been derived from MyBaseForm to make sure it will have all the same properties. But we have modified the widget of one of the fields.
Now you can update your view. Please note, this is untested Python + Pseudocode
views.py
def myFormView(request):
if request.method == 'POST': # If the form has been submitted...
form = MyBaseForm(request.POST)
#submit button has not been pressed, so the dropdown has triggered the submission.
#Hence we won't safe the form, but reload it
if 'my_real_submitbotton' not in form.data:
if 'param1' == "Dropdown":
form = MyDropDownForm(request.POST)
else:
#do your normal form saving procedure
else:
form = ContactForm() # An unbound form
return render(request, 'yourTemplate.html', {
'form': form,
})
This mechanism does the following:
When the form is submitted it checks if you have pressed the "submit" button or have used a dropdown onChange to trigger a submission. My solution doesn't contain the javascript code you need to trigger the submission with an onChange. I just like to provide a way to solve it.
To use the 'my_real_submitbutton' in form.data construct you will be required to name your submit button:
<input type="submit" name="my_real_submitbutton" value="Submit" />
Of course you can choose any string as Name. :-)
In case of a submit by your dropdown field you must check which value has been selected in this drop down menu. If this value satisfies the condition you want to return a Dropdown Menu you create an instance of DropDownForm(request.POST) otherwise you can leave everything as it is and rerender your template.
On the downside this will refresh your page.
On the upside it will keep all the already entered field values. So no harm done here.
If you would like to avoid the page refresh you can keep my proposed idea but you need to render the new form via AJAX.

Dynamic show and hide fields in Django admin panel

I have defined model in which one of the filed has definition:
REPEAT = (
('day', 'Daily'),
('week', 'Weekly'),
)
repeats = models.CharField('Repeat', default='day', max_length=5, choices=REPEAT)
Also I have defined related admin model, which is responsible to show my main model in panel.
Is possible to show and hide some fields in admin panel based on choice in repeats field? For example in scenery when user choose 'Daily', then some fields are not required and I want to hide them.
I will be thankful for any advices or hints.
Yes, you can add custom JS to your admin model:
class MyModelAdmin(admin.ModelAdmin):
class Media:
js = ("my_code.js",)
STATIC_URL is appended to your filename automagically.
And your JS function, assuming jQuery, something like:
$(function(){
$('<my-selector>').change(function(){
//do something on select change
});
});

GenericForeignKey and Admin in Django

Let's say I have a Post object that can contain Images, Videos, and other media types. I can use a GenericForeignKey to link them together. Something like:
class Post(models.Model):
title = models.CharField(...)
text = models.TextField(...)
class AudioMedia(models.Model):
...
class VideoMedia(models.Model):
...
class ImageMedia(models.Model):
...
class MediaObject(models.Model):
post = models.ForeignKey(Post)
order = models.IntegerField()
content_type_media = models.ForeignKey(
ContentType, limit_choices_to={
'model__in': (
'audiomedia',
'imagemedia',
'videomedia')
})
object_id_media = models.PositiveIntegerField()
obj = generic.GenericForeignKey('content_type_media', 'object_id_media')
Now I can easily create an admin interface, like:
class MediaObjectAdminInLine(admin.StackedInline):
model = MediaObject
ct_field = "content_type_media"
ct_fk_field = "object_id_media"
extra = 0
class PostAdmin(admin.ModelAdmin):
inlines = [MediaObjectAdminInLine]
Now the question :) In admin/, I can easily create a new Post. To the post, I can easily add more MediaObject. In the panel, I have a drop down menu to chose the type (audio, video, ...), but I have to manually enter the ID of the object I want to link with Post.
I have tried various extensions, including grappelli. Some provide the ability to lookup the ID of objects to link here. I want the ability to add objects here, eg, add an AudioMedia, a VideoMedia, an ImageMedia, depending on what I pick from the dropdown.
Any suggestions?
You'd need to quite a bit of work to get this going.
You're asking that the admin dynamically display a modelform, based on what model type you chose from a drop down.
Django's admin does not do that (nor do any known extensions to it).
To make this work, you'll have to:
Write a custom JavaScript event handler which captures the onchange of the model select drop down.
Then calls Django's admin and requests the inline modelform for that model.
Updates the current HTML page with that model form.
Then you'll need to intercept the parent model's modelform's save() method to figure out which child modelform it's dealing with, and correctly save it to the database.
Then you'll need to sort out how to get the parent model's modelform to correctly display the appropriate child model's modelform dependent on the model of the child.
Sound daunting? It is.
Here's an easier way:
Just have a single "Media" model. You'll have a few fields on the model that are only valid for one of your types (though there's plenty of crossover).
Name any fields that are specific to a single Media type with a prefix for that mediatype, i.e. image_size', orvideo_title`.
Attach a JavaScript handler to your ModelAdmin which selectively shows and hides fields based on a dropdown for the media type. Something like this:
class MediaAdmin(admin.ModelAdmin):
class Meta:
js = ["js/media-types.js",]
// media-type.js
(function($) {
$(document).ready(function(){
$('.module[id^=module] .row').hide();
$('.module[id^=module] .row.module').show();
$('.module[id^=module] .row.module select').each(function(){
if ($(this).val() != '')
{
var group = $(this).parent().parent().parent().parent();
var field = $(this).parent().parent().parent();
var mtype = $(this).val().toLowerCase();
if (mtype != '')
{
$('.row', group).not(field).slideUp('fast');
$('.row[class*="'+mtype+'"]', group).slideDown('fast');
$('.row[class*="all"]', group).slideDown('fast');
}
else
{
$('.row', group).not(field).slideUp('fast');
}
}
});
$('.module[id^=module] .row.module select').change(function(){
var group = $(this).parent().parent().parent().parent();
var field = $(this).parent().parent().parent();
var mtype = $(this).val().toLowerCase();
if (mtype != '')
{
$('.row', group).not(field).slideUp('fast');
$('.row[class*="'+mtype+'"]', group).slideDown('fast');
$('.row[class*="all"]', group).slideDown('fast');
}
else
{
$('.row', group).not(field).slideUp('fast');
}
});
});
})(django.jQuery);
django-admin-genericfk doesn't work with Django 1.9.
Other than that I only found the following module:
https://github.com/lexich/genericrelationview
which looks well maintained. Unfortunately, its JS code does not work well with how Django CMS sets up jQuery (noConflict jQuery), so it seems that it is not an option for me. But it should be fine if not used in Django CMS pages but the regular Django Admin.
I realize this is pretty old, but this is still the first result when searching for this.
django-admin-genericfk does exactly what you need.