Overriding the admin Media class - django

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

Related

Model "help" text in Django Inline Admin

I'm using an inline admin in my Django application. I want to have some help text displayed in the admin form for Page to go with the inline admin (not just the individual help text for each field within that model). I've been trying to figure out how to do this, but cannot seem to find anything on the issue. Am I missing some trivial out-of-the box option for doing this?
If there's no super simple way to do this, is there a way to do this by extending some template?
Below are parts of my models and their admins:
class Page(models.Model):
....
class File(models.Model):
page = models.ForeignKey(Page)
....
class FileAdminInline(admin.TabularInline):
model = File
extra = 0
class PageAdmin(admin.ModelAdmin):
inlines = (FileAdminInline,)
If you're not talking about specific help_text attribute then then look at this post it shows an underdocumented way of accomplishing this.
If you don't want to mess around with getting the help_text information into the formset's context and modify the edit_inline template, there is a way of capturing the verbose_name_plural Meta attribute of your model for that purpose.
Basic idea: If you mark that string as safe you can insert any html element that comes to your mind. For example an image element with it's title set to global your model help text. This could look somethink like this:
class Meta:
verbose_name = "Ygritte"
verbose_name_plural = mark_safe('Ygrittes <img src="' + settings.STATIC_URL + \
'admin/img/icon-unknown.svg" class="help help-tooltip" '
'width="15" height="15" '
'title="You know nothing, Jon Snow"/>')
Of course - this is kind of hacky - but this works quite simple, if your model is only accessed as an inline model and you don't need the plural verbose name for other things (e.g. like in the list of models in your application's admin overview).

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.

Custom Django Inline Admin

Let's say I have these two models:
class Egg(models.Model):
# some fields
class Spam(models.Model):
egg = models.ForeignKey(Egg)
img = models.ImageField()
I planned to have the spams inlined to egg in admin site. The problem is I also want a very customized method on uploading the spam images (like this), like having my own view and template. So far, I just got :
class CustomInline(admin.StackedInline):
model = Spam
template = 'admin/app/inline.html' # empty
class EggAdmin(admin.ModelAdmin):
inlines = [CustomInline, ]
The idea is having some kind of gallery of spams and custom image upload in egg admin. (Is this achievable?)
So the questions are:
I want to injects variables to be available on the template (spam objects on inline.html for gallery) . Is there a way to do this?
Is it okay to POST something to a view (upload process)? Or that particular view must be registered first on admin site or something?
I've looked on InlineAdmin source but still have no idea what to do/override
Thanks
using the form property you can subclass your ModelForm and completely change the way your inline form works.

Django Admin: Add text at runtime next to a field

I want to add a text next to a field of the django admin interface.
The warning needs to created at runtime inside a python method. I know python and the django ORM well, but I don't know how to get the text next the field.
The text should be a warning. Raising ValidationError in clean() is not a solution, since
the user can't edit the page any more. It should be just a warning message.
You can use custom ModelForm subclass for the admin, adding help_text attribute for the field in question at its initialization, and style it appropriately.
# forms.py
class YourModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(YourModelForm, self).__init__(*args, **kwargs)
self.fields['field_in_question'].help_text = generate_warning()
# admin.py
class YourModelAdmin(admin.ModelAdmin):
form = forms.YourModelForm
# And here you can specify custom CSS / JS which would make
# `help_text` for that particular field look like a warning.
# Or you can make it generic--say, style (and maybe reposition w/js) all tags
# like <span class="warning"> that occur within the help text of any field.
class Media:
css = {"all": ("admin_warning.css", )}
js = ("admin_warning.js", )
If you want to do it in changelist view, you can write in model method, which returns string in format you want, and put name of that method in list_display in admin.
class MyModel(models.Model):
myfield = models.CharField(max_length=100)
def myfield_with_warning(self):
return '%s - %s' % (self.myfield, '<span class="warn">My warning message</p>'
myfield_with_warning.short_description = 'My field verbose name'
myfield_with_warning.allow_tags = True
class MyModelAdmin(models.ModelAdmin):
list_display = ('myfield_with_warning',)
If it's not what you need, write more precisely, where do you want to display warning message.
I think the simplest way would be to override the specific admin page for that model. This is described here in the Django documentation. The template you need to override is probably change_form.html. Within these template displayed object is available in the template variable original.
I would add a method or property to you model, that generates and returns the error message and call this method from the template.
Edit: Have a look at contrib/admin/templates/admin/change_form.html there is a include for includes/fieldset.html that displays the the fields of the admin site. You could put some code there that chckes if the model has some special named attribute and if so it is displayed. You could them simply override that change_form.html for all models with your custom one.

process the media class of a model form in django to a template

I have a form looking like this:
class MarketingActionForm(forms.ModelForm):
contact = ManyToManyByLetter(Contact, field_name="first_name")
#contact = AjaxManyToManyField(Contact, DICT_LOOKUP)
class Meta:
model = MarketingAction
exclude = ('created_by',)
class Media:
js = (
settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js",
settings.MEDIA_URL + "js/jquery.js",
settings.MEDIA_URL + "js/ajax_filtered_fields.js",
)
I process this form with a view to the template. Now I`m wondering why the Media class is not automatically processed in the template, at least it does not show up in the .html output.
Therefore i want to ask what i have to do in order that the media definitions will show up in the .html output.
I did not find it in the django .docs. Therefore i thought it will be processed automatically.
You'll need to add {{form.media}} in the template yourself. References to form media are not inserted automatically.
It would be very hard to do since entire html document including the <head> section is to be typed by the template designer and django would have to guess where to insert the links if it were to attempt to do it automatically (it would be especially hard to guess correctly for the javascript media - if there are dependencies between scripts)
I believe the Media class is used in the admin, by classes which subclass admin.ModelAdmin
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-media-definitions