Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 278
magiambelli Avatar asked Dec 17 '12 01:12

magiambelli


2 Answers

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:

  1. Write a custom JavaScript event handler which captures the onchange of the model select drop down.
  2. Then calls Django's admin and requests the inline modelform for that model.
  3. Updates the current HTML page with that model form.
  4. 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.
  5. 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);
like image 69
Jack Shedd Avatar answered Nov 16 '22 02:11

Jack Shedd


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.

like image 42
Risadinha Avatar answered Nov 16 '22 00:11

Risadinha