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

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();'})
}

Related

Call js function every time DjangoForm is loaded?

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)

Display inline model in the details page of the parent model? Flask, SQLAlchemy

I have a model A, which incorporates an inline model B that allows users to enter some texts. Currently, the data users added into the inline model can only be shown in the 'edit' page of the parent model A, but not in the 'details' page. Is there a way to solve this?
edit-page
Add the field in model B to the column_details_list (docs)
Add the same field in model B to the column_formatters_detail dictionary (docs), specifying a formatter method that returns appropriate HTML.
For example:
from markupsafe import Markup
class ExampleView(AdminView):
# include the comments child field plus any parent fields from model A you want to show
column_details_list = ('name', 'last_name', 'comments')
def _comments_formatter(view, context, model, name):
# model is parent model A
_html = []
if model.comments:
# return any valid HTML markup
for _comment_model in model.comments:
# add html para per comment
_html.append(f'<p>User:{str(_comment_model.user)}, Comment:{_comment_model.comment}</p>')
return Markup(''.join(_html))
column_formatters_detail = {
'comments': _comments_formatter
}

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.

Overriding the admin Media class

Given an admin media class that sets up a rich text editor, like:
class TutorialAdmin(admin.ModelAdmin):
fields...
class Media:
js = ['/paths/to/tinymce.js',]
I would like the ability to selectively override js depending on a field value in the model it references. I've added a "use_editor" boolean to the Tutorial model. The question is, how can I detect whether the current instance has that bool set? I'd like to end up with something like:
class Media:
if self.use_editor:
js = ['/path/to/tinymce.js',]
else:
js = ''
Ideas? Thanks.
Many thanks to Sam Lai on django-users, I finally have a working solution for this. Turns out to be trickier than expected because you can't directly access field values on the instance from within the Admin class - you need to do it by redefining the form used by the Admin class. In addition, you'll need to use _media rather than "class Media:" to set the media property.
The goal is to detect the current instance value of the use_visual_editor field and turn javascript paths on or off depending on its value (so authors can turn off the visual editor on a per-record basis). Here's the final working solution:
models.py
class Tutorial(models.Model):
use_visual_editor = models.BooleanField()
forms.py
from django import forms
from tutorials.models import Tutorial
class TutorialAdminForm(forms.ModelForm):
class Meta:
model = Tutorial
def _media(self):
if self.instance.use_visual_editor == True:
js = ['/paths/to/javascript',]
else:
js = ['']
return forms.Media(js=js)
media = property(_media)
admin.py
from django import forms
....
class TutorialAdmin(admin.ModelAdmin):
form = TutorialAdminForm
Works perfectly!
An alternative approach, given you're using TinyMCE, is to use an additional JS file that adds a 'mceNoEditor' class to textareas you don't want to convert to rich text.
eg
class fooAdmin(admin.Modeladmin)
class Media:
js = ['/path/to/admin-styling.js',
'/paths/to/tinymce.js',]
In your tinymce.js init, you need to ensure there's a class defined for disabling the editor, such as:
editor_deselector : "mceNoEditor",
and in the admin-styling.js file have some kind of jQuery call in the document ready handler that finds certain elements and adds that class before TinyMCE is invoked.
Usually you can do this with the 'id_foo' identifier. eg, if you have a model field called additional_notes:
$('textarea#id_additional_notes').addClass('mceNoEditor');
It's possible to use more sophisticated jQuery selectors too, of course.
HTH
Steve