This is my models
from django.db import models
class Page(models.Model):
page_id = models.IntegerField(default=0)
class Question(models.Model):
page = models.ForeignKey(Page)
question = models.CharField(max_length=150)
class Option(models.Model):
question = models.ForeignKey(Question)
option = models.CharField(max_length=100)
image_class = models.CharField(max_length=75)
this is my admin.py
from django.contrib import admin
from .models import Page, Question, Option
class OptionInline(admin.StackedInline):
model = Option
extra = 1
class QuestionInline(admin.StackedInline):
model = Question
extra = 1
inlines = [OptionInline]
class PageAdmin(admin.ModelAdmin):
inlines = [QuestionInline]
admin.site.register(Page, PageAdmin)
basically i want this multi level relation to appear as a multi level inline in the admin site. can someone please help out
Instead of using nested inlines, Django 1.8 provides the InlineModelAdmin.show_change_link
from django.contrib import admin
from .models import Page, Question, Option
class OptionInline(admin.StackedInline):
model = Option
extra = 1
class QuestionInline(admin.StackedInline):
model = Question
extra = 1
show_change_link = True
class PageAdmin(admin.ModelAdmin):
inlines = [QuestionInline,]
admin.site.register(Page, PageAdmin)
class QuestionAdmin((admin.ModelAdmin):
inlines = [OptionInline,]
admin.site.register(Question, QuestionAdmin)
This way, when you save the Page model -having completed the inline Question model- a link called 'change' will appear at the saved instance of the inline Question model. Clicking it, you will land at the main page of the Question model instance with the Option model as inline.
When you complete the Option model inline and hit the 'save and continue editing', the back button should return you to the relevant Page instance.
There is also a post which describes how you can achieve the same result if you use previous Django versions.
Django does not support it out of the box, but there is project called django-nested-inline that will do the job. Also you can make your own solution.
Related
I using a nested model in a Django project.
The following snippet code is models.py:
from django.db import models
from django.db.models.deletion import CASCADE
class Model_(models.Model):
name = models.CharField(max_length=50, default="This is a model")
frequently = models.FloatField(default=1.0)
def __str__(self):
return self.name
class SubModel(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=8, default='0x')
model_ = models.ForeignKey(Model_, on_delete=CASCADE)
def __str__(self):
return self.name
class Metadata(models.Model):
key = models.CharField(max_length=100)
value = models.CharField(max_length=100)
sub_model = models.ForeignKey(SubModel, on_delete=CASCADE)
This is my admin.py script:
from django.contrib import admin
from nested_inline.admin import NestedTabularInline, NestedStackedInline,\
NestedModelAdmin
from <djano-application-name>.models import Model_, SubModel, Metadata
class MetadataAdmin(NestedTabularInline):
model = Metadata
extra = 1
class SubModelAdmin(NestedStackedInline):
model = SubModel
inlines = [MetadataAdmin]
extra = 1
class Model_Admin(NestedModelAdmin):
model = Model_
inlines = [SubModelAdmin]
list_display = ['name']
admin.site.register(Model_, Model_Admin)
Question:
What is the difference between NestedStackedInline and NestedTabularInline in admin.py script?
[NOTE]:
Versions: Python 2.7 and Django 1.11
If you are using django-nested-inline, It means you wanted to edit models on the same page as a parent model and add more than 1 level of children at once with the parent object in admin.
The Django admin is just a normal Django application and you can't have a second level of inlines(nested forms) in the default Django admin.
The difference between NestedStackedInline and NestedTabularInline is just Layout. Indeed, both work exactly the same behind the scenes, the only difference is the template used for rendering. Check the official docs. So, picking one for your project is only a matter of preference regarding the interface layout.
This is how NestedStackedInline will look, each field of the model is under other.
and this is NestedTabularInline, each field of the model is in one line, column wise
I know this issue has been asked more than once, but as Django is evolving with new version, I'll ask the question again :
I am using the model User (Django User, not in my models.py) and create another model with a Foreign key to User.
models.py :
class Plan(models.Model):
user = models.ForeignKey(User)
I can simply display every Plan in my user by doing this in admin.py :
class PlanInline(admin.TabularInline):
model = Plan
extra = 0
class MyUserAdmin(UserAdmin):
ordering = ('-date_joined', 'username')
inlines = [PlanInline,]
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
But things are about to get more tricky. I want to add a model that has a foreign key pointing to Plan :
class Order(models.Model):
plan = models.ForeignKey('Plan')
And I want to be able to see all Orders for each Plan. As of today, it is impossible to have nested inlines in Django Admin (without editing the HTML, which I want to avoid) :
User
-> Plan 1
-> Order 1
-> Order 2
-> Plan 2
-> Order 3
So my idea is to display in the User Admin only A LINK for each plan, to the page to edit Plans, and put Orders as inline :
class OrderInline(admin.TabularInline):
model = Order
extra = 0
class PlanAdmin(admin.ModelAdmin):
inlines = [OrderInline,]
admin.site.register(Plan, PlanAdmin)
The question is, how do I display a link to a Plan in my User Admin?
class MyUserAdmin(UserAdmin):
ordering = ('-date_joined', 'username')
??? LINK ????
I saw some solutions on this topic : Django InlineModelAdmin: Show partially an inline model and link to the complete model, but they are a bit "dirty' as they make us write HTML and absolute path into the code.
Then I saw this ticket on Djangoproject : https://code.djangoproject.com/ticket/13163. It seems exactly what I'm looking for, and the ticket is "fixed". So I tried adding like in the fix show_change_link = True :
class PlanInline(admin.TabularInline):
model = Plan
extra = 0
show_change_link = True
class MyUserAdmin(UserAdmin):
ordering = ('-date_joined', 'username')
show_change_link = True
inlines = [UserProfileInline, PlanInline]
But it doesn't work (and I have no log or error).
Is there any way to do this in a clean way?
Update for django 1.8
show_change_link = True
https://github.com/django/django/pull/2957/files
I suggest adding a custom PlanInline method that returns the link and see if it helps. Something along these lines:
from django.utils.safestring import mark_safe
from django.core.urlresolvers import reverse
class PlanInline(TabularInline):
model = Plan
readonly_fields = ('change_link',)
...other options here...
def change_link(self, obj):
return mark_safe('Full edit' % \
reverse('admin:myapp_plan_change',
args=(obj.id,)))
Basically all we do here is create the custom method that returns a link to the change page (this specific implementation is not tested, sorry if there is any parse error but you get the idea) and then add it to the readonly_fields as described here: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields
A couple of notes for the change_link method: You need to replace 'myapp' in the view name with your actual application name. The mark_safe method just marks the text as safe for the template engine to render it as html.
I have a Django related question about foreign keys in the admin panel. I'm facing the following situation:
class Driver(models.Model):
name = models.CharField(max_length=200)
executable = models.CharField(max_length=200)
class Device(models.Model):
name = models.CharField(max_length=200)
bound_driver = models.ForeignKey(Driver)
class DriverAssignment(models.Model):
device = models.ForeignKey(Device)
driver = models.ForeignKey(Driver)
Every device needs to have a bound driver (which it uses). DriverAssignment should be the table which shows which driver can be used by which device. So one device can have multiple possibilities of drivers which can be bound. Now i would like to have a dropdown on my admin panel showing all possible drivers for a specific device to select the 'bound_driver'.
How can i do this in Django? This is probably an easy thing for an experienced Django guy. I hope someone can give me a hint since i'm kind of new to Django. Thanks a lot!
For Django >1.8
Use the InlineModelAdmin (docs for 2.2) as explained there:
models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
admin.py
from django.contrib import admin
class BookInline(admin.TabularInline):
model = Book
class AuthorAdmin(admin.ModelAdmin):
inlines = [
BookInline,
]
Change your model Structure to This:
class Driver(models.Model):
name = models.CharField(max_length=200)
executable = models.CharField(max_length=200)
class Device(models.Model):
name = models.CharField(max_length=200)
bound_driver = models.ForeignKey(Driver, related_name="bound_to")
available_drivers = models.ManyToManyfield(Driver)
ManyToManyField would do the same work as DriverAssignment Table.
You can add Available drivers in Available drivers field.
But then You would also Want that bound_driver is one of the Available Drivers. This validation you will have to do in forms. For that you have to over-ride Admin forms. See links
Links of Reference:
ManytoMany field: https://docs.djangoproject.com/en/1.6/ref/models/fields/#django.db.models.ManyToManyField
Model Admin (to over-ride admin functionality):
https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#modeladmin-objects
You will have to spend some time reading and implementing if you want ot learn more. :)
OR
If you want to go with the same structure, than you will have to over-ride the form in ModelAdmin see here and Provide you custom form, which will be something like this:
class CustomForm(ModelForm)
bound_driver = forms.ModelChoiceField(queryset = <your custom queryset that returns only available drivers>, ...)
class Meta:
model = Device
https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form
There is a snippet for inverse inlines. If you still need it you may try this:
https://gist.github.com/mzbyszewska/8b6afc312b024832aa85
It has been used by me for OneToOneField in django 1.5 and 1.6. Unfortunately I did not test it for ForeignKeyField, but the one of the previous users claims that it works for ForeignKeyField either.
The best description of the snippet is contained in it. The Person class is your DriverAssignment class and Device correspond to the Address class in the example below:
Example:
from django.db import models
class Address(models.Model):
street = models.CharField(max_length = 255)
zipcode = models.CharField(max_length = 10)
city = models.CharField(max_length = 255)
class Person(models.Model):
name = models.CharField(max_length = 255)
business_addr = models.ForeignKey(Address,
related_name = 'business_addr')
home_addr = models.OneToOneField(Address, related_name = 'home_addr')
other_addr = models.OneToOneField(Address, related_name = 'other_addr')
You use reverseadmin in the following way:
from django.contrib import admin
from django.db import models
from models import Person
from reverseadmin import ReverseModelAdmin
class AddressForm(models.Form):
pass
class PersonAdmin(ReverseModelAdmin):
inline_type = 'tabular'
inline_reverse = ('business_addr', ('home_addr', AddressForm), ('other_addr' (
'form': OtherForm
'exclude': ()
)))
admin.site.register(Person, PersonAdmin)
inline_type can be either "tabular" or "stacked" for tabular and
stacked inlines respectively.
I'm trying to add an extra input to a admin.ModelAdmin for a model I have so I can record some optional text when another input has changed.
I can't get the custom ModelForm recognised as name 'EquipmentAdmin' is not defined. I've tried several different ways of importing but think I must have missed something obvious. It feels like there's a circular reference between the EquipmentAdmin and EquipmentAdminForm as they both include a reference to each other in the code.
I have created my Django application Flightdeck and have these all in the same folder;
models.py
from django.db import models
class Location(models.Model):
name = models.CharField(max_length=45)
class Equipment(models.Model):
unit_id = models.CharField(max_length=45)
located = models.ForeignKey(Location)
located_from = models.DateField()
class EquipmentLocationHistory(models.Model):
unit = models.ForeignKey(Equipment)
located = models.ForeignKey(Location)
start = models.DateField()
end = models.DateField()
change_reason = models.CharField(max_length=45)
admin.py
from django.contrib import admin
from flightdeck.models import *
from flightdeck.forms import EquipmentAdminForm
class EquipmentAdmin(admin.ModelAdmin):
form = EquipmentAdminForm
def save_model(self, request, obj, form, change):
if 'located' in form.changed_data:
try:
old = Equipment.objects.get(unit_id=obj.unit_id)
except Equipment.DoesNotExist:
old = None
if old:
if 'located' in form.changed_data:
located_history = EquipmentLocationHistory(unit=obj, located=old.located, start=old.located_from, end=obj.located_from)
located_history.save()
obj.save()
forms.py
from django import forms
from django.contrib import admin
class EquipmentAdminForm(forms.ModelForm):
reason = forms.CharField()
class Meta:
model = EquipmentAdmin
I would like to include the reason value when I add the EquipmentLocationHistory but can't test what I have as the EquipmentAdminForm isn't loaded.
EquipmentAdmin is not a model. Your ModelForm needs to reference Equipment
from django import forms
from django.contrib import admin
from flightdeck.models import Equipment
class EquipmentAdminForm(forms.ModelForm):
reason = forms.CharField()
class Meta:
model = Equipment
PS: when you have circular references, there are many ways around the problem. The best way with model imports and django is to use django.db.models.get_model('app', 'model')
I need a nested django admin inline,
which I can include the date field inlines in an other inline like below.
I have the models below:
class Person(models.Model):
name = models.CharField(max_length=200)
id_no = models.IntegerField()
class Certificate(models.Model):
cerfificate_no = models.CharField(max_length=200)
certificate_date = models.DateField(max_length=100)
person = models.ForeignKey(Person)
training = models.CharField(max_length=200)
class Training_Date(models.Model):
date = models.DateField()
certificate = models.ForeignKey(Certificate)
And, the admin below:
class CertificateInline(admin.StackedInline):
model = Certificate
class PersonAdmin(admin.ModelAdmin):
inlines = [CertificateInline,]
admin.site.register(Person,PersonAdmin)
But, I need to include the Training_Date model as inline which is part of Certificate admin inline.
Any idea?
There has been some movement in https://code.djangoproject.com/ticket/9025 recently, but I wouldn't hold my breath.
One common way around this is to link to an admin between first and second (or second and third) level by having both a ModelAdmin and an Inline for the same model:
Give Certificate a ModelAdmin with TrainingDate as an inline. Set show_change_link = True for CertificateInline so you can click on an inline to go to its ModelAdmin change form.
admin.py:
# Certificate change form has training dates as inline
class TrainingDateInline(admin.StackedInline):
model = TrainingDate
class CertificateAdmin(admin.ModelAdmin):
inlines = [TrainingDateInline,]
admin.site.register(Certificate ,CertificateAdmin)
# Person has Certificates inline but rather
# than nesting inlines (not possible), shows a link to
# its own ModelAdmin's change form, for accessing TrainingDates:
class CertificateLinkInline(admin.TabularInline):
model = Certificate
# Whichever fields you want: (I usually use only a couple
# needed to identify the entry)
fields = ('cerfificate_no', 'certificate_date')
# Django 1.8 introduced this, no need to make your own link
show_change_link = True
class PersonAdmin(admin.ModelAdmin):
inlines = [CertificateLinkInline,]
admin.site.register(Person, PersonAdmin)
More universal solution
from django.utils.safestring import mark_safe
from django.urls import reverse
class EditLinkToInlineObject(object):
def edit_link(self, instance):
url = reverse('admin:%s_%s_change' % (
instance._meta.app_label, instance._meta.model_name), args=[instance.pk] )
if instance.pk:
return mark_safe(u'edit'.format(u=url))
else:
return ''
class MyModelInline(EditLinkToInlineObject, admin.TabularInline):
model = MyModel
readonly_fields = ('edit_link', )
class MySecondModelAdmin(admin.ModelAdmin):
inlines = (MyModelInline, )
admin.site.register(MyModel)
admin.site.register(MySecondModel, MySecondModelAdmin)
AFAIK, you can't have a second level of inlines in the default Django admin.
The Django admin is just a normal Django application, so nothing prevents you from implementing a second level of nested forms, but IMHO it would be a kind of convoluted design to implement. Perhaps that is why there is no provision for it.
pip install django-nested-inline
This package should do what you need.
Nested inlines are provided at:
https://github.com/BertrandBordage/django-super-inlines/
pip install django-super-inlines
A more up to date solution (february 2021) is to use the show_change_link config variable: https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.show_change_link
This does exactly the same as the EditLinkToInlineObject proposed in solutions above, but is less code and is probably well tested by Django Developers
You would just have to define show_change_link=True in each one of your inlines
UPDATE (January 25th, 2022):
Here's the updated link in the docs (Django 4.0): https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.show_change_link
Use django-nested-admin which is the best package to do nested inlines.
First, install "django-nested-admin":
pip install django-nested-admin
Then, add "nested_admin" to "INSTALLED_APPS" in "settings.py":
# "settings.py"
INSTALLED_APPS = (
# ...
"nested_admin", # Here
)
Then, add "path('_nested_ad..." to "urlpatterns" in "urls.py":
# "urls.py"
from django.urls import include, path
urlpatterns = [
# ...
path('_nested_admin/', include('nested_admin.urls')), # Here
]
Finally, extend "NestedTabularInline" with "Training_DateInline()" and "CertificateInline()" classes and extend "NestedModelAdmin" with "PersonAdmin()" class in "admin.py" as shown below:
# "admin.py"
from .models import Training_Date, Certificate, Person
from nested_admin import NestedTabularInline, NestedModelAdmin
class Training_DateInline(NestedTabularInline):
model = Training_Date
class CertificateInline(NestedTabularInline):
model = Certificate
inlines = [Training_DateInline]
#admin.register(Person)
class PersonAdmin(NestedModelAdmin):
inlines = [CertificateInline]
I used the solution provided by #bigzbig (thank you).
I also wanted to go back to the first list page once changes had been saved so added:
class MyModelInline(EditLinkToInlineObject, admin.TabularInline):
model = MyModel
readonly_fields = ('edit_link', )
def response_post_save_change(self, request, obj):
my_second_model_id = MyModel.objects.get(pk=obj.pk).my_second_model_id
return redirect("/admin/mysite/mysecondmodel/%s/change/" % (my_second_model_id))