django admin for abstract base class?

2019-05-24 04:19发布

问题:

Suppose I have car - suv/bus models.

I'd like to list all cars in the django admin (with name attribute).
When user clicks one of the car, it would go to the appropriate detail page of either suv or bus model.

How do I create a such admin list page?

class Car:

    name = models.CharField()

    class Meta:
        abstract=True


class Suv(Car):

    pass

class Bus(Car):

    pass

回答1:

Not sure this is the best approach, but sharing my solution here.

First, create a Database View

create view [app_label_model_name] as

select id, name, 1 as car_type
from suv
union
select id, name, 2 as car_type
from bus
order by something;

then create non-manged model

 class PostBaseView(models.Model):
     # this would be CarBaseView, I'm copying my actual code
     id = models.IntegerField()
     raw_html = models.TextField(null=True)
     created_at = models.DateTimeField(primary_key=True)
     post_type = models.IntegerField()

     class Meta:
         managed = False

then, from admin page, change the links based on subclass types.

 class ChangeList(ChangeListDefault):

     def url_for_result(self, result):
         # pk = getattr(result, self.pk_attname)
         id = result.id

         app_label = result.get_app_label()
         model_name = result.get_model_name()

         return reverse('admin:%s_%s_change' % (app_label,
                                                model_name),
                        args=(quote(id),))


 class PostBaseViewAdmin(admin.ModelAdmin):

     list_display = ['__str__', 'post_type_str']

     class Meta:
         model = PostBaseView

     def get_changelist(self, request, **kwargs):
         """
         Returns the ChangeList class for use on the changelist page.
         """
         return ChangeList

 admin.site.register(PostBaseView, PostBaseViewAdmin)

volla you have a admin that shows multiple subclasses at one list.



回答2:

You need to make your Car not an abstract model, as you have to have some base table for your vehicles.

class Car:
    name = models.CharField()
    class Meta:
        abstract = False

class Suv(Car):
    pass

class Bus(Car):
    pass

class CarAdmin(admin.ModelAdmin):
    model = Car

But here the easy things end. The admin doesn't have a built-in solution for your task. I suggest you overriding at least the CarAdmin.get_form() and CarAdmin.get_queryset() methods, though I'm not sure they are the only ones you have to customize.



回答3:

eugene's answer took me to the right direction. Thank you.

I'm not aware if there is a better way either, but in my case this approach solved the problem.

I already had a base class (Project) with some generic fields (SportProject and others), and specific classes that extends Project with their fields, and I wanted to have a single list of project, but wanted to have specific edit form for each project type.

In app/admin.py file, I did:

from django.contrib.admin.views.main import ChangeList as ChangeListDefault
from django.urls import reverse

class ProjectChangeList(ChangeListDefault):
    def url_for_result(self, result):
        app_label = result._meta.app_label.lower()
        model_name = result.children._meta.object_name.lower()
        return reverse('admin:%s_%s_change' % (app_label, model_name), args=(result.id,))

class ProjectDataAdmin(admin.ModelAdmin):
    #...
    def get_changelist(self, request, **kwargs):
        """
        Returns the ChangeList class for use on the changelist page.
        """
        return ProjectChangeList

In my model Project, I have a children property that returns the children instance (SportProject for instance). Not sure if there is another way to have this structure in Django.

I also had all classes registered in django-admin (Project and all children, as SportProject), to have django-admin pages for those classes. Thus, I'm using django-modeladmin-reorder to hide undesired links on django-admin main page.

Hope it helps someone.



回答4:

I think this answer is applicable. ModelAdmin needs to know foreign key entries will be readonly, specified in a tuple called readonly_fields.

Using the problem that brought me here and there, I have (models.py):

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)

    def __str__(self):
        return self.choice_text

class Answer(models.Model):    
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE, default = 1)

    class Meta:
        abstract = True

class Vote(Answer):
    choice = models.ForeignKey(Choice, on_delete=models.CASCADE)

    def answer(self):
        return self.choice

    def __str__(self):
        return self.choice.choice_text

And (admin.py):

class VoteAdmin(admin.ModelAdmin):
    #list_display = ('Answer.question.question_text', 'Answer.User.user_id', 'Choice.choice_text')
    readony_fields = ('question', 'user')
    list_display = ('question', 'user', 'choice')
    fieldsets = [
        ('Question', {'fields': ['question']}),
        ('User', {'fields': ['user']}),
        ('Vote', {'fields' : ['choice']}),
    ]

Hope this proves useful to future searchers.