Django Grappelli Tabular Inline add new row TinyMCE textfield not editable
Asked Answered
M

3

14

I am using django Grappelli skin for my project.

I have a ModelAdmin with tabular inline function.

I use extra = 0 to prevent auto insert blank row, when the page is loaded. It works fine.

Now, when I click on the + sign to insert new row, the row is loaded, but the tinymce textfield is not editable.

Anyone know what is the reasons and how to solve this problem?

After reading the document:

http://django-grappelli.readthedocs.org/en/latest/customization.html#using-tinymce

I notice:

Using TinyMCE with Inlines is a bit more tricky because of the hidden empty-form. You need to write a custom template and use the inline-callbacks to

onInit: remove TinyMCE instances from the the empty-form.

onAfterAdded: initialize TinyMCE instance(s) from the form.

onBeforeRemoved: remove TinyMCE instance(s) from the form.

TinyMCE with Inlines is not supported by default.

Any sample for this? I notice it is a TinyMCE functions that I need to change.

Magisterial answered 21/4, 2011 at 0:28 Comment(2)
Does anyone here know how to solve this problem?Magisterial
I'm running into the same problem. any update?Sello
S
14

It looks like some of the CSS classes and HTML structures used by Grappelli have changes since Almflm's solution was written. However, I was able to modify hir solution to work with Grappelli v2.4.7, and simplified the implementation in the process.

Setup

  1. Override the relevant template by copying /PATH/TO/grappelli/templates/admin/edit_inline/stacked.html to /PATH/TO/YOURMOD/templates/admin/edit_inline/
  2. In your site's settings.py, ensure that YOURMOD is above grappelli in INSTALLED_APPS. Otherwise, Django will continue using the Grappelli version of the template.

Code

Now you just need to make two changes to your copy of stacked.html. Find the block of javascript that begins:

$("#{{ inline_admin_formset.formset.prefix }}-group").grp_inline({

...and make the following changes inside that block:

  1. Add an onBeforeAdded function like this (or modify the existing function if one exists, but I didn't have one):

        onBeforeAdded:function(form) {
            // New inlines start as a hidden template with class grp-empty-form.
            // This contains a textarea, which TinyMCE converts correctly, but
            // something about the transformation from template to visible 
            // form breaks TinyMCE, so we need to remove it from the template and 
            // then re-add it after the transformation. 
            // c.f. http://stackoverflow.com/questions/5738173/
            if (tinyMCE != undefined) {
                django.jQuery('.grp-empty-form').find('textarea').each(function() { 
                    var tid = django.jQuery(this).attr("id");
                    tinyMCE.execCommand("mceRemoveControl",false,tid); 
                });
            }
        },
    
  2. Add the following to the onAfterAdded function (you should already have one, so be sure to modify the existing one rather than defining a new one!):

            if (tinyMCE != undefined) {
              // re-initialise tinyMCE instances
              deselector = tinyMCE.settings.editor_deselector;
              django.jQuery(form).find('textarea:not(.'+deselector+')').each(function(k,v) {
                var tid = django.jQuery(this).attr('id');
                tinyMCE.execCommand('mceAddControl', false, tid);
              });
            }
            // This line is optional. It just ensures that the new inline appears
            // un-collapsed, even if inlines are collapsed by default
            django.jQuery(form).removeClass("grp-closed").addClass("grp-open");
    

That's it!

EDIT Added the deselector to the onAfterLoad - ensures you can still define a deselector class in a tinymce config file, and inlines will conform to this.

Sabina answered 29/10, 2013 at 16:3 Comment(2)
For tinymce 4.x, use mceAddEditor and mceRemoveEditor as command name instread of mceAddControl and mceRemoveControl respectivelyCaprification
This works but I'm not sure I'm a fan of this approach because you'll have to remember to update this template whenever you upgrade Grappelli.Newmark
L
4

I didn't have time to look into this more thoroughly, so I'm pretty sure there's a better solution, but this seems to work for me (tested with django-grappelli 2.3.5 and django-tinymce 1.5.1a2.

I'm assuming that you are using stacked inlines.

You have to override a template from grappelli, templates/admin/edit_inline/stacked.html. Inside the for-loop iterating over inline_admin_formset|formsetsort:sortable_field_name, right after the nested for-loop iterating over inline_admin_form, add this snippet:

{% if forloop.last %}
  <script type="text/javascript">
    if (tinyMCE != undefined) {
      django.jQuery('textarea', '.empty-form').each(function() {
        tinyMCE.execCommand('mceRemoveControl', false, django.jQuery(this).attr('id'));
      });
    }
  </script>
{% endif %}

it should disable tinyMCE controls for the textarea elements in the hidden 'empty-form', initialized by inline javascript rendered for the tinyMCE widget(s).

somewhere around the line 133 in the original grappelli template you'll see an invocation of grp_inline(). Add/modify the arguments:

$("#{{ inline_admin_formset.formset.prefix }}-group").grp_inline({
  prefix: "{{ inline_admin_formset.formset.prefix }}",
  onBeforeRemoved: function(f) {
    if (tinyMCE != undefined) {
      // make sure tinyMCE instances in empty-form are inactive
      django.jQuery('textarea', '.empty-form').each(function() {
        tinyMCE.execCommand('mceRemoveControl', false, django.jQuery(this).attr('id'));
      });
    }
  },
  [...]
  onAfterAdded: function(form) {
    if (tinyMCE != undefined) {
      // re-initialise tinyMCE instances
      $('textarea', form).each(function(k,v) {
        var tid = $(this).attr('id');
        tinyMCE.execCommand('mceRemoveControl', false, tid);
        tinyMCE.execCommand('mceAddControl', false, tid);
      });
      // make sure tinyMCE instances in empty-form are inactive
      django.jQuery('textarea', '.empty-form').each(function() {
        tinyMCE.execCommand('mceRemoveControl', false, django.jQuery(this).attr('id'));
      });
    }
    [...]
  }
  [...]

If you use sortables, you'd also want to disable tinyMCE controls on textareas of the inline being dragged. Look for the sortable() initialisation, and modify the 'start' callback:

start: function(evt, ui) {
  ui.placeholder.height(ui.item.height() + 12);
  if (tinyMCE != undefined) {
    // make sure tinyMCE instances in empty-form are inactive
    $('textarea', ui.item).each(function(k,v) {
      var tid = $(this).attr('id');
      tinyMCE.execCommand('mceRemoveControl', false, tid);
    });
  }
},
[...]

This should give the rough idea how to work around this pesky problem...

Ladysmith answered 15/10, 2011 at 22:5 Comment(1)
This does not appear to work with the following versions: Django==1.4, django-tinymce==1.5.1b2, django-grappelli==2.4.0Sello
B
0

This is the only solution that works for the following setup:

  • Django==4.2
  • django-grappelli==3.0.6
  • django-nested-admin==4.0.2
  • tinyMCE version 3
// tinymce_setup.js
tinymce.init({
  // make sure you do not have `selector` defined here.
  mode: "none",
});

(function ($) {
  $(document).ready(function () {
    $(".djn-inline-form:not(.grp-empty-form) textarea").each(function () {
      // initialize MCE manually here rather than in `init` call.
      tinyMCE.execCommand("mceAddControl", false, this.id);
    });
  });
})(django.jQuery || window.jQuery);

(function ($) {
  $(document).on("formset:added", function (event, $form) {
    // initialize MCE manually when new formset is added
    $(".djn-inline-form:not(.grp-empty-form) textarea").each(function () {
      tinyMCE.execCommand("mceAddControl", false, this.id);
    });
  });
})(django.jQuery);

I am using django-nested-admin so make sure to adjust the target CSS class for your needs.

As it's stated in previous answer comments section you may need to adjust execCommand params if you are using other version of tinyMCE.

Django Nested Admin Github's issue that helped with finding the solution.

No need to override templates, just include this file as per Django Grappelli documentation

Buff answered 25/5, 2023 at 0:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.