I want to add Many2Many fields editable in Django admin list_display page.
The model structure as below,
class Genre(models.Model):
name = models.CharField(max_length=250, unique=True)
class Movie(models.Model):
name = models.CharField(max_length=250)
genre = models.ManyToManyField(Genre)
And I tried as,
class MovieAdmin(admin.ModelAdmin):
list_display = ['name', 'genre']
list_editable = ['genre']
Above throws an error.
Django by default won't allow to add ManyToManyField in list_editable in ModelAdmin. So we need to override model admin methods.
On looking your models you need to follow below steps to get the ManyToManyField editable in list display page.
In apps/forms.py you need to define which ManyToMany fields you need to make editable in list display page. As below,
from django import forms
from app.models import Genre
class MovieChangeListForm(forms.ModelForm):
# here we only need to define the field we want to be editable
genre = forms.ModelMultipleChoiceField(queryset=Genre.objects.all(),
required=False)
In app/admin.py you need to override methods of model admin. As below,
from django.contrib import admin
from django.contrib.admin.views.main import ChangeList
from app.models import Movie
from app.forms import MovieChangeListForm
class MovieChangeList(ChangeList):
def __init__(self, request, model, list_display,
list_display_links, list_filter, date_hierarchy,
search_fields, list_select_related, list_per_page,
list_max_show_all, list_editable, model_admin):
super(MovieChangeList, self).__init__(request, model,
list_display, list_display_links, list_filter,
date_hierarchy, search_fields, list_select_related,
list_per_page, list_max_show_all, list_editable,
model_admin)
# these need to be defined here, and not in MovieAdmin
self.list_display = ['action_checkbox', 'name', 'genre']
self.list_display_links = ['name']
self.list_editable = ['genre']
class MovieAdmin(admin.ModelAdmin):
def get_changelist(self, request, **kwargs):
return MovieChangeList
def get_changelist_form(self, request, **kwargs):
return MovieChangeListForm
admin.site.register(Movie, MovieAdmin)
Now you all set to check the changes, run server and check django admin for Movie model. You can edit ManyToMany field directly from list display page.
Note : If you are going to use muliptle ManyToManyFields editable in list then, you need to set DATA_UPLOAD_MAX_NUMBER_FIELDS in settings.py .
As far as I understand, you want to add the model in your admin page.
to do so, you need to simply do this in your admin.py file inside your django app
admin.site.register(MovieAdmin)
It is automatically editable. Hope this helps.
Related
I'm developing a site where I can manually add photographers to the Django adminpanel and then upload the images taken by them.
I used the default User system in Django and added a field here that shows this user is a photographer or not :
from django.contrib.auth.models import AbstractUser
# Create your models here.
class User(AbstractUser):
is_photographer = models.BooleanField(default=True)
Also, in the model that I need to put the images, I used ManyToMany field for photographers (because these photos may have been taken by several people) :
from registration import User
class Photo(models.Model):
...
photographers = models.ManyToManyField(User)
The problem is that when adding a new image to the admin panel, users who are not photographers and the is_photographer is False, is also displayed in the Photographers. I want only users who are photographers to be displayed and not normal users.
You can use Django formfield. Django has placed these formfields for different types of fields. To use formfields in ManyToMany relationships, you can use formfield_for_manytomany in the model admin_class.
Try putting this function in the Photo model admin class in the admin.py file of the application where the Photo model is located, and if you don't have a class admin, create one like this:
from .models import Photo
from django.contrib import admin
from registration.models import User
class PhotoAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
kwargs["queryset"] = User.objects.filter(is_photographer=True)
return super(PhotoAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
admin.site.register(Photo, PhotoAdmin)
In your admin.py file do this:
class PhotoAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
kwargs['queryset'] = Users.objects.filter(is_photographer=True)
return super().formfield_for_manytomany(db_field, request, **kwargs)
admin.site.register(Photo, PhotoAdmin)
You can work with the limit_choices_to=… [Django-doc]:
from django.conf import settings
class Photo(models.Model):
# …
photographers = models.ManyToManyField(
setting.AUTH_USER_MODEL,
limit_choices_to={'is_photographer': True}
)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
I want to customize Django Admin to have specific section for objects of my models (Such as Post or Product models) that use as an archive section.
I now that, I need one field in my models that shown status of objects (Such as is_archive field), but I don't have any idea about how to display them in Django Admin.
Does anyone have an opinion on this?
Create Proxy model for model you need
Create separate section in your admin panel for this proxy model
Override get_queryset() for it.
models.py
from django.db import models
class Post(models.Model):
...
is_archive = models.BooleanField(default = False)
...
class PostProxy(Post):
class Meta:
proxy = True
admin.py
from django.contrib import admin
from .models import *
#admin.register(Post)
class PostAdmin(admin.ModelAdmin):
...
#admin.register(PostProxy)
class PostProxyAdmin(admin.ModelAdmin):
...
def get_queryset(self, request):
return super().get_queryset(request).filter(is_archive=True)
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.
Apparently Django's ModelAdmin/ModelForm doesn't allow you to use save_m2m() if there's an intermediate through table for a ManyToManyField.
models.py:
from django.db import models
def make_uuid():
import uuid
return uuid.uuid4().hex
class MyModel(models.Model):
id = models.CharField(default=make_uuid, max_length=32, primary_key=True)
title = models.CharField(max_length=32)
many = models.ManyToManyField("RelatedModel", through="RelatedToMyModel")
def save(self, *args, **kwargs):
if not self.id:
self.id = make_uuid()
super(GuidPk, self).save(*args, **kwargs)
class RelatedModel(models.Model):
field = models.CharField(max_length=32)
class RelatedToMyModel(models.Model):
my_model = models.ForeignKey(MyModel)
related_model = models.ForeignKey(RelatedModel)
additional_field = models.CharField(max_length=32)
admin.py:
from django import forms
from django.contrib import admin
from .models import MyModel
class RelatedToMyModelInline(admin.TabularInline):
model = MyModel.many.through
class MyModelAdminForm(forms.ModelForm):
class Meta:
model = MyModel
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
inlines = (RelatedToMyModelInline, )
admin.site.register(MyModel, MyModelAdmin)
If I save MyModel first and then add a new related through model via the inline it works fine, but if I try to set the inline while also adding data for a new MyModel, I get the Django Admin error "Please correct the error below." with nothing highlighted below.
How can I have it save MyModel and then save the inline intermediary models after? Clearly Django can save the through model once it has saved MyModel - so I'm just looking for a hook into that. I tried overriding the form's save() method by calling save_m2m() after calling instance.save(), but apparently that doesn't work for M2Ms with a through table.
I'm using Django 1.2, but this is still an issue in 1.3.
UPDATE: Well, I made a test app like above to isolate the problem, and it appears that it works as expected, correctly saving the M2M intermediary object after saving the MyModel object... as long as I let Django automatically create the MyModel.id field when running python manage.py syncdb - once I added the GUID id field, it no longer works.
This smells more and more like a Django bug.
In your MyModelAdmin you might try overriding the save_formset method. This way you can choose the order in which you save.
I have a model named Project which has a m2m field users. I have a task model with a FK project. And it has a field assigned_to. How can i limit the choices of assigned_to to only the users of the current project?
You could do this another way, using this nifty form factory trick.
def make_task_form(project):
class _TaskForm(forms.Form):
assigned_to = forms.ModelChoiceField(
queryset=User.objects.filter(user__project=project))
class Meta:
model = Task
return _TaskForm
Then from your view code you can do something like this:
project = Project.objects.get(id=234)
form_class = make_task_form(project)
...
form = form_class(request.POST)
You need to create a custom form for the admin.
Your form should contain a ModelChoiceField in which you can specify a queryset parameter that defines what the available choices are. This form can be a ModelForm.
(the following example assumes users have an FK to your Project model)
forms.py
from django import forms
class TaskForm(forms.ModelForm):
assigned_to = forms.ModelChoiceField(queryset=Users.objects.filter(user__project=project))
class Meta:
model = Task
Then assign the form to the ModelAdmin.
admin.py
from django.contrib import admin
from models import Task
from forms import TaskForm
class TaskAdmin(admin.ModelAdmin):
form = TaskForm
admin.site.register(Task, TaskAdmin)