jazzband / django-polymorphic

Improved Django model inheritance with automatic downcasting
https://django-polymorphic.readthedocs.io
Other
1.63k stars 278 forks source link

M2M field in model admin #182

Open eriktelepovsky opened 8 years ago

eriktelepovsky commented 8 years ago

I have a simple ManyToManyField to a polymorphic model.

When I assign at least one reference to this field and open django admin to change the object, I got this exception:

AttributeError at /en/admin/shop/membership/10/change/
'int' object has no attribute 'pk'

Stacktrace:

/Users/erik/env/inventorwp/lib/python2.7/site-packages/django/forms/models.py in __init__
            object_data = model_to_dict(instance, opts.fields, opts.exclude) ...

/Users/erik/env/inventorwp/lib/python2.7/site-packages/django/forms/models.py in model_to_dict
                    data[f.name] = list(qs.values_list('pk', flat=True)) ...

/Users/erik/env/inventorwp/lib/python2.7/site-packages/django/db/models/query.py in __iter__
        self._fetch_all() ...

/Users/erik/env/inventorwp/lib/python2.7/site-packages/django/db/models/query.py in _fetch_all
            self._result_cache = list(self.iterator()) ...

/Users/erik/env/inventorwp/src/django-polymorphic/polymorphic/query.py in iterator
            real_results = self._get_real_instances(base_result_objects) ...

/Users/erik/env/inventorwp/src/django-polymorphic/polymorphic/query.py in _get_real_instances
            ordered_id_list.append(base_object.pk) ...
vdboor commented 8 years ago

Do you still get this error with the latest 0.8 release (designed for Django 1.9)

joshuajonah commented 8 years ago

I do.

In an admin view: Traceback:

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
  149.                     response = self.process_exception_by_middleware(e, request)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
  147.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/contrib/admin/options.py" in wrapper
  541.                 return self.admin_site.admin_view(view)(*args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/utils/decorators.py" in _wrapped_view
  149.                     response = view_func(request, *args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/views/decorators/cache.py" in _wrapped_view_func
  57.         response = view_func(request, *args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/contrib/admin/sites.py" in inner
  244.             return view(request, *args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/contrib/admin/options.py" in change_view
  1438.         return self.changeform_view(request, object_id, form_url, extra_context)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/utils/decorators.py" in _wrapper
  67.             return bound_func(*args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/utils/decorators.py" in _wrapped_view
  149.                     response = view_func(request, *args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/utils/decorators.py" in bound_func
  63.                 return func.__get__(self, type(self))(*args2, **kwargs2)

File "/usr/lib/python3.4/contextlib.py" in inner
  30.                 return func(*args, **kwds)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/contrib/admin/options.py" in changeform_view
  1395.                 form = ModelForm(instance=obj)

File "/var/www/vhosts/nctools/apps/crm/admin.py" in __init__
  56.         super(CategoryForm, self).__init__(*args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/forms/models.py" in __init__
  282.             object_data = model_to_dict(instance, opts.fields, opts.exclude)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/forms/models.py" in model_to_dict
  105.                     data[f.name] = list(qs.values_list('pk', flat=True))

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/db/models/query.py" in __iter__
  258.         self._fetch_all()

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/db/models/query.py" in _fetch_all
  1074.             self._result_cache = list(self.iterator())

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/polymorphic/query.py" in iterator

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/polymorphic/query.py" in _get_real_instances
  198.         idlist_per_model = defaultdict(list)

Exception Type: AttributeError at /admin/crm/category/8627/change/
Exception Value: 'int' object has no attribute 'pk'

In a CBV: Traceback:

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
  149.                     response = self.process_exception_by_middleware(e, request)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
  147.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/views/generic/base.py" in view
  68.             return self.dispatch(request, *args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/braces/views/_access.py" in dispatch
  98.             request, *args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/braces/views/_access.py" in dispatch
  337.             request, *args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/views/generic/base.py" in dispatch
  88.         return handler(request, *args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/views/generic/edit.py" in get
  275.         return super(BaseUpdateView, self).get(request, *args, **kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/views/generic/edit.py" in get
  213.         return self.render_to_response(self.get_context_data())

File "/var/www/vhosts/nctools/apps/projects/views.py" in get_context_data
  73.         kwargs = super(GroupsMembershipsMixin, self).get_context_data(**kwargs)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/views/generic/edit.py" in get_context_data
  122.             kwargs['form'] = self.get_form()

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/views/generic/edit.py" in get_form
  74.         return form_class(**self.get_form_kwargs())

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/forms/models.py" in __init__
  282.             object_data = model_to_dict(instance, opts.fields, opts.exclude)

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/forms/models.py" in model_to_dict
  105.                     data[f.name] = list(qs.values_list('pk', flat=True))

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/db/models/query.py" in __iter__
  258.         self._fetch_all()

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/django/db/models/query.py" in _fetch_all
  1074.             self._result_cache = list(self.iterator())

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/polymorphic/query.py" in iterator

File "/var/www/vhosts/nctools/lib/python3.4/site-packages/polymorphic/query.py" in _get_real_instances
  198.         idlist_per_model = defaultdict(list)

Exception Type: AttributeError at /projects/close/task/3112/
Exception Value: 'int' object has no attribute 'pk'
vdboor commented 8 years ago

@joshuajonah : could you please post a simple version of your model+admin code that reproduces this bug?

MarkAWard commented 7 years ago

Is there any solution for the admin to work with M2M references to polymorphic models? I don't get the above error but instead I'm getting AttributeError: 'Somemodel_polymorphic_model' object has no attribute 'get_real_concrete_instance_class' as Im attempting to define an a M2M inline admin using the through table between the polymorphic_model and Somemodel so the aforementioned method is not being called on an instance of a parent or child polymorphic model but rather on a row in the through table.

MarquisT commented 7 years ago

I get the same thing. I am trying to use an M2M field that references a polymorphic model.

model = defaults['instance'].get_real_concrete_instance_class() # respect proxy models AttributeError: 'Articles_media' object has no attribute 'get_real_concrete_instance_class

MarquisT commented 7 years ago

This is from my Admin class class MediaInline(StackedPolymorphicInline): """ An inline for a polymorphic model. The actual form appearance of each row is determined by the child inline that corresponds with the actual model type. """

fields = ('cutline', 'uploadDate')

class PhotoInline(StackedPolymorphicInline.Child):
    model = Photo
    fields = ['cutline', 'uploadDate', 'thumb_media_html']
    readonly_fields = ['thumb_media_html']
    #fields = ('cutline', 'uploadDate', 'thumb_media_html')

class VideoInline(StackedPolymorphicInline.Child):
    model = Video

'''
def get_queryset(self, request):
    qs = Media.objects.filter(article__exact = None)
    #print(qs)
    qs = super(MediaInline, self).get_queryset(request)
    return qs
'''

model = Articles.media.through
child_inlines = (
    PhotoInline,
    VideoInline,
)

class ArticleAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):

#form = ArticleForm

inlines = [MediaInline]
verbose_name_plural = 'Article'
list_display = ['title', 'author', 'publicationDate', 'picCount']

Below is my models:

class Media(PolymorphicModel):

cutline = models.CharField("Cutline", max_length = 1012)
uploadDate = models.DateField()

def __str__(self):
    return self.cutline

@property        
def media_html(self):
    return format_html("<P>" + self + "</P>")

@property
def thumb_media_html(self):
    return format_html("<P>" + self + "</P>")

class Video(Media):

url = models.FileField(upload_to=user_directory_path, max_length=250, storage = UploadStorage())

def __str__(self):
    if self.cutline:
        return self.cutline
    return self.url.name

Inherits from Media so we also allow Video types

class Photo(Media):

#picture_root = settings.PICTURE_DIRECTORY
imagefile = models.FileField(upload_to=user_directory_path, max_length=250, storage = UploadStorage())
thumbnail = models.FileField(upload_to=user_directory_path, max_length=250, storage = UploadStorage())
#media_html.allow_tags = True 

def __str__(self):
    if self.cutline:
        #print("In the function a " + self.cutline)
        return self.cutline
    #print("No cutline found" + self.imagefile.name)
    return self.imagefile.name
    #return self.cutline

@property
def thumb_media_html(self):
    #url = static("sl_images/"+self.thumbnail.name)
    url = settings.NEWS_MEDIA_URL + settings.SL_NEWS_PICS + self.thumbnail.name
    print(url)
    return format_html("<IMG SRC='{}' class='articleThumbnail' />",
        mark_safe(url)
    )

@property        
def media_html(self):
    print("Doing an image")

    #url = static("sl_images/"+self.imagefile.name)
    url = settings.NEWS_MEDIA_URL + settings.SL_NEWS_PICS + self.imagefile.name

    print(url)
    return format_html("<IMG SRC='{}' class='articleImage img-responsive' />",
        mark_safe(url)
    )

    return "<P>Image</P>"

class Articles(models.Model):

class Meta: permissions = (
    ("view_article", "Can view full articles"),
    ("demo_article", "Can demo a limited number of articles")
)

media = models.ManyToManyField(Media, related_name='article', blank=True)
title = models.CharField("Title", max_length = 255)
story = models.TextField("Article", max_length = 45)
category = models.SmallIntegerField("Category", null=True)
picCount = models.IntegerField(default = 0, editable = False)  
length = models.IntegerField(default = 0, editable = False) 
author = models.ForeignKey(settings.AUTH_USER_MODEL)
uploadDate = models.DateField()
publicationDate = models.DateField()
oldarticle = models.SmallIntegerField("Unused", null=True, blank=True)

def __str__(self):              # __unicode__ on Python 2
    return self.title

@property
def getFirstMedia(self):
    a = self.media.first()
    return self.media.first()

@property
def getRestMedia(self):
    a = self.media.all()[1:]
    return a

objects = ArticlesManager()

@property
def getTitleText(self):
    return mark_safe(self.title)

@property
def getArticleText(self):
    return mark_safe(self.story)

def getUnassignedImages(self):

    media = Media.objects.filter(article_id = None)
    print(media)
    return media
MarkAWard commented 7 years ago

@MarquisT I was able to get my admin to work using a horizontal filter with PolymorphicChildModelAdmin and PolymorphicParentModelAdmin instead of StackedPolymorphicInline.Child, it's not the nice stacked inline you were going for but this at least worked for me:

class MediaFieldAdmin(PolymorphicChildModelAdmin):
    base_model = Media

class PhotoFieldAdmin(MediaFieldAdmin):
    model = Photo

class VideoFieldAdmin(MediaFieldAdmin):
    model = Video

admin.site.register(Photo, PhotoFieldAdmin)
admin.site.register(Video, VideoFieldAdmin)

class MediaFieldParentAdmin(PolymorphicParentModelAdmin):
    base_model = Media
    child_models = (
        RequirementDegreeField,
    )
    list_filter = (PolymorphicChildModelFilter,)

admin.site.register(Media, MediaFieldParentAdmin)

class ArticleAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):
    model = Articles
    verbose_name_plural = 'Article'
    list_display = ['title', 'author', 'publicationDate', 'picCount']
    filter_horizontal = ('media', )