Displaying foreign model fields in django admin as editable - python

I have a Django Model with a Foreign key:
class Library:
name=models.CharField()
class Book:
title=models.CharField()
library=models.ForeignKey(Library)
models.py
class BookAdmin(admin.ModelAdmin):
extra = 0
fields = ['title', 'library__name'] # library__name not found
admin.site.register(Book, BookAdmin)
admin.py
In the admin, I want to display Book and show an editable field for Library.name in the Book view (not the other way around with inlines):
> Book
* Title: "Foo"
* Library Name: "Bar"
As readonly, it's easy (just creating a method in Book model returning the library name value) but I cannot make it work for editable fields, I tried using fields=('title','library.name') and fields=('title','library__name') without success

You need an inline model admin:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#inlinemodeladmin-objects

try to use related_name in models like that
library=models.ForeignKey(Library, related_name="library")
then use fields=('title','library__name') and it should work.

Related

Django InlineModelAdmin not displaying inlineformset correctly

I'm developing a Django application in which a lot of models have foreign keys and m2m relationships. This results in many ModelChoiceField being displayed in the Django admin for my models. To make model choice more bearable, I installed the django-select2 app in my project.
I have been trying to implement select2 in the inline forms the admin site displays when editing related objects, but the form doesn't render the ModelSelect2Widget (it renders a simple select; it doesn't even include the select2 library).
What I tried was creating a ModelForm in forms.py overriding the relevant fields widgets, then, using inlineformset_factory, had a variable holding the factory class. Lastly, in admin.py, added my custom inline formset using the formset property of the InlineModelAdmin class.
forms.py
class FichaTecnicaForm(forms.ModelForm):
class Meta:
model = models.FichaTecnica
exclude = ('pelicula',)
widgets = {
'responsable': ModelSelect2Widget,
'cargo': ModelSelect2Widget,
'pais': ModelSelect2Widget
}
FichaTecnicaInline = inlineformset_factory(models.Pelicula, models.FichaTecnica, form=FichaTecnicaForm)
admin.py
class FichaTecnicaInline(admin.TabularInline):
model = models.FichaTecnica
formset = forms.FichaTecnicaInline
extra = 0
# Some other code here
# This is where the inlines are invoked
class PeliculaAdmin(admin.ModelAdmin):
inlines = [
FichaTecnicaInline,
# some other inlines, not relevant...
]
I was expecting that the inline form set would display the select2 widget for the model choice, but instead it displays a standard select widget.
Thank you very much in advance for you help!
I think there is an error in your code where your FichaTecnicaInline class is overwritten by your admin class definition.
Maybe the formset class created by inlineformset_factory, which uses your custom form is being overwritten by defaults from admin.TabularInline. I think the first thing to try is giving them different names.

Django - Change Fields in Group Admin Model

The built-in django group model on the admin site only shows name:
but I want to include additional fields that are already part of the group model, such as id.
I have tried adding these fields using the following admin.py setup:
from django.contrib import admin
from django.contrib.auth.models import Group
class GroupsAdmin(admin.ModelAdmin):
list_display = ["name", "pk"]
class Meta:
model = Group
admin.site.register(Group, GroupsAdmin)
But this returns the error:
django.contrib.admin.sites.AlreadyRegistered: The model Group is already registered.
I have successfully registered other models (I've created) on admin but the above doesn't work for those models that are already a part of django.
How can I add fields in the admin model for Group?
The accepted answer is correct, however, I would like to point out that you could inherit from the GroupAdmin if your goal is only extending that is, and not modifying:
from django.contrib.auth.admin import GroupAdmin
class GroupsAdmin(GroupAdmin):
list_display = ["name", "pk"]
admin.site.unregister(Group)
admin.site.register(Group, GroupsAdmin)
You need to unregister it first from the built-in Group model and then register it again with your custom GroupAdmin model.
So:
class GroupsAdmin(admin.ModelAdmin):
list_display = ["name", "pk"]
class Meta:
model = Group
admin.site.unregister(Group)
admin.site.register(Group, GroupsAdmin)
Also, the Meta class is not required. You can remove it.

Indirect inline in Django admin

I have the following models:
class UserProfile(models.Model):
user = models.OneToOneField(User)
class Property(models.Model):
user = models.ForeignKey(User)
I would like to create a TabularInline displaying every Property connected to a particular UserProfile on its Django admin page. The problem here is, of course, that Property does not have a ForeignKey directly to UserProfile, so I cannot simply write
class PropertyTabularInline(admin.TabularInline):
model = Property
class UserProfileAdmin(admin.ModelAdmin):
inlines = (PropertyTabularInline,)
How can I easily do what I want?
You can overwrite the User admin page to display both the Profile and the Property models.
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from myapp.models import *
class ProfileInline(admin.TabularInline):
model = Profile
class PropertyInline(admin.TabularInline):
model = Property
class UserAdmin(UserAdmin):
inlines = (ProfileInline, PropertyInline,)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
You can also remove any unwanted/unused User properties from being displayed (e.g. Groups or Permissions)
more here: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#extending-the-existing-user-model
and here:
https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#a-full-example
class PropertyTabularInline(admin.TabularInline):
model = Property
def formfield_for_dbfield(self, field, **kwargs):
if field.name == 'user':
# implement your method to get userprofile object from request here.
user_profile = self.get_object(kwargs['request'], UserProfile)
kwargs["queryset"] = Property.objects.filter(user=user_profile)
return super(PropertyInLine, self).formfield_for_dbfield(field, **kwargs)
once this is done, you can add this inline to user UserProfileAdmin like:
class UserProfileAdmin(admin.ModelAdmin):
inlines = (PropertyTabularInline,)
Haven't tested it, but that should work.
It is achievable by making one change in your models.
Instead of creating OneToOne relationship from UserProfile to User, subclass User creating UserProfile. Code should look like that:
class UserProfile(User):
# some other fields, no relation to User model
class Property(models.Model):
user = models.ForeignKey(User)
That will result in creating UserProfile model that have hidden OneToOne relation to User model, it won't duplicate user model.
After doing that change, your code will work. There are some changes under the hood, like UserProfile no longer have it's own ID, you can access fields from User inside UserProfile and it's hard to swap User model using settings.AUTH_USER_MODEL (that will require creating some custom function returning proper type and changing migration by hand) but if this is not a problem for you, it may be good solution.

How can I automatically create list_filters for all my models in a django admin page?

I have a Django site, using admin to manage the models. Currently, I am using
from django.db.models.base import ModelBase
for name, var in databuild_models.__dict__.items():
if type(var) is ModelBase:
admin.site.register(var)
To register all the models in the module with the admin module. However, I want to automatically add list_filters for every attribute on each model. I've read https://docs.djangoproject.com/en/1.8/ref/contrib/admin/ where they use the form:
class MymodelAdmin(admin.ModelAdmin):
list_filter = ['model_name_attribute']
and then
admin.site.register(models.MyModel, MyModelAdmin)
Is there a way to similarly loop through each attribute in a model, and add it to a filter list?
For example:
filter_list_fields = []
for attr in MyModel:
filter_list_fields.append(str(attr))
list_filter = filter_list_fields
and then register them as a general class?
Firstly, you can use the applications api to get a list of models in your app, rather than inspecting the module's __dict__
In Django 1.8+, you can get a list of field names for a model using Model._meta.get_fields method. I've excluded the automatically generated id field, and you might find you want to make further improvements.
In python, you can create a class dynamically using type. You need the name of the new class (not important here), the base classes that it inherits from (in this case Admin.ModelAdmin, and a dictionary of attributes (in this case, there is only one attribute, list_filter).
Finally, register the model with the custom model admin we have created.
Putting it together, your admin.py should look something like this:
from django.contrib import admin
from django.apps import apps
app_config = apps.get_app_config('myapp')
models = app_config.get_models()
for Model in models:
fieldnames = [f.name for f in Model._meta.get_fields() if f.name != 'id']
ModelAdmin = type('ModelAdmin', (admin.ModelAdmin,), {'list_filter': fieldnames})
admin.site.register(Model, ModelAdmin)

Django inline link to model editing

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.

Categories

Resources