Is it possible to have many-to-many widget in admin-panel in user-tab to pick groups, that user belong to, and similar many-to-many widget in group-tab to pick users, which should belong to that group?
There's the easy way and the hard way.
The easy way is to use Django's InlineModelAdmin objects. This way, however, you cannot use the group widget.
from django.contrib.auth.admin import GroupAdmin
from django.contrib.auth.models import User, Group
class UserSetInline(admin.TabularInline):
model = User.groups.through
raw_id_fields = ('user',) # optional, if you have too many users
class MyGroupAdmin(GroupAdmin):
inlines = [UserSetInline]
# unregister and register again
admin.site.unregister(Group)
admin.site.register(Group, MyGroupAdmin)
The hard way requires you to build your own form, manually load and save the related users:
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import GroupAdmin
from django.contrib.auth.models import User, Group
class GroupForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
label='Users',
queryset=User.objects.all(),
required=False,
widget=admin.widgets.FilteredSelectMultiple(
"users", is_stacked=False))
class Meta:
model = Group
exclude = () # since Django 1.8 this is needed
widgets = {
'permissions': admin.widgets.FilteredSelectMultiple(
"permissions", is_stacked=False),
}
class MyGroupAdmin(GroupAdmin):
form = GroupForm
def save_model(self, request, obj, form, change):
# save first to obtain id
super(GroupAdmin, self).save_model(request, obj, form, change)
obj.user_set.clear()
for user in form.cleaned_data['users']:
obj.user_set.add(user)
def get_form(self, request, obj=None, **kwargs):
if obj:
self.form.base_fields['users'].initial = [o.pk for o in obj.user_set.all()]
else:
self.form.base_fields['users'].initial = []
return GroupForm
# unregister and register again
admin.site.unregister(Group)
admin.site.register(Group, MyGroupAdmin)
Related
I want to implement autocomplete_fields feature but it doesn't work. I assume it happens because of Proxy model.
So I have Customer Proxy model and PromoCode model. PromoCode has FK to Customer model. And I need to have search field for customers in PromoCode change form. Here are models and admin classes:
class User(AbstractUser):
# bunch of fields
class Customer(User):
class Meta:
proxy = True
class CustomerAdmin(admin.ModelAdmin):
search_fields = ['email',]
admin.site.register(Customer, CustomerAdmin)
class PromoCode(TimeStampedModel):
customer = models.ForeignKey(User, on_delete=PROTECT, null=True, blank=True)
class PromoCodeAdmin(admin.ModelAdmin):
autocomplete_fields = ('customer',)
admin.site.register(PromoCode, PromoCodeAdmin)
This code gives error:
: (admin.E039) An admin for model "User" has to be registered to be referenced by PromoCodeAdmin.autocomplete_fields.
But I can't change model in customer field to Customer, becase when I run migration it breaks with following error:
ValueError: The field coupons.PromoCode.customer was declared with a lazy reference to 'users.customer', but app 'users' doesn't provide model 'customer'
Also I can't register User as admin class, because I don't need it to be registered. I register Customer model.
What can I do to solve such case?
It is not possible (see: https://code.djangoproject.com/ticket/30666). I got around this by registering the User admin, but making it redirect to my custom admin model. I also removed all of the actions on the user admin:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.urls import reverse
admin.site.unregister(User)
#admin.register(User)
class UserAdmin(BaseUserAdmin):
preserve_filters = False
def get_actions(self, request):
actions = super().get_actions(request)
if "delete_selected" in actions:
del actions["delete_selected"]
return actions
def has_delete_permission(self, request, obj=None):
return False
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def changelist_view(self, *args, **kwargs):
return HttpResponseRedirect(reverse("admin:core_domainuser_changelist"))
Django==2.2.1
GDAL==2.3.2
django-username-email==2.2.4
I have a simple Django application with a custom user model based on django-username-email's AbstractCUser, which removes the username from the user model, using e-mail address instead. On the user model, I defined a PointField field storing the user's current location.
models.py
from django.contrib.gis.db import models as gis_models
from cuser.models import AbstractCUser
class User(AbstractCUser):
"""Custom user model that extends AbstractCUser."""
current_location = gis_models.PointField(null=True, blank=True,)
I would like to register this model in Django admin so that I can register new users and view/set their location with a map widget. This kind of works if I use a custom user admin based on admin.OSMGeoAdmin in combination with a custom user change form:
admin.py
from django.contrib.gis import admin
from django.contrib.auth import get_user_model
from .forms import CustomUserCreationForm, CustomUserChangeForm
class CustomUserAdmin(admin.OSMGeoAdmin):
model = get_user_model()
add_form = CustomUserCreationForm # <- there seems to be a problem here
form = CustomUserChangeForm
list_display = ['email', 'last_name', 'first_name']
readonly_fields = ['last_login', 'date_joined']
admin.site.register(get_user_model(), CustomUserAdmin)
forms.py
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = get_user_model()
exclude = ('username',)
class CustomUserChangeForm(UserChangeForm):
class Meta(UserChangeForm.Meta):
model = get_user_model()
fields = (
'email',
'first_name',
'last_name',
'current_location',
# ...
)
When I open an existing user record in the Django admin, the required fields are displayed as intended, and the current location is displayed on a map. However, the same form seems to be used for user creation as well (i.e. add_form has no effect), which makes it impossible to add new users via the admin, because the password setting functionality is not embedded correctly (see screen shot).
The problem seems to be that OSMGeoAdmininherits from ModelAdmin, which in contrast to the standard UserAdmindoes not have an add_form property.
Is there any way to specify a custom user creation form in this case (ideally the UserCreationForm provided by django-username-email while maintaining the ability to display point fields on a map on the user change form?
You need to override get_form similar to how django.contrib.auth.admin.UserAdmin does.
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during user creation
"""
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
Following schillingt's suggestion, this is the code I ended up using:
from django.contrib.gis import admin
from django.contrib.auth import get_user_model
from cuser.forms import UserCreationForm
from .forms import CustomUserChangeForm
class CustomUserAdmin(admin.OSMGeoAdmin):
model = get_user_model()
add_form = UserCreationForm
form = CustomUserChangeForm
list_display = ['email', 'last_name', 'first_name']
readonly_fields = ['last_login', 'date_joined']
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during user creation.
Override get_form method in the same manner as django.contrib.auth.admin.UserAdmin does.
"""
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
admin.site.register(get_user_model(), CustomUserAdmin)
I'm extending the UserProfile but I'm having trouble getting the new field - current_article - that I added to list_display to show properly in the user overview page -
Home › Auth › Users
The new fields has it's own column but always with value (None) even after selecting values in the user detail page.
How can I get the field's values to show up in overview admin page?
I referenced this stackoverflow question:
Django Admin: how to display fields from two different models in same view?
Here is the code:
#admin.py
class UserProfileInline(admin.StackedInline):
model = UserProfile
class CustomUserAdmin(UserAdmin):
inlines = [
UserProfileInline,
]
def current_article(self,instance):
return instance.user.current_article
list_display = ('id','username','email','current_article','first_name','last_name','is_active', 'date_joined', 'is_staff','last_login','password')
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
And in Models.py
#models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class UserProfile(models.Model):
user = models.OneToOneField(User)
current_article = models.ForeignKey(Article,blank=True,default=1)
def __unicode__(self):
return "{}".format(self.user)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
Your method is actually raising an AttributeError exception, but Django hides this when processing list_display (It catches all exceptions and returns None as the value).
You need to have return instance.get_profile().current_article.
My application has some hundred users and about 10 to 20 groups. Now the customer wants to give special privileges to group leaders.
How would you do this (in a reusable way)?
You can create a new model that extends the group class and add it a new foreign key to a user (the leader)
#------------------------------------------
# models.py of fooapp
from django.db import models
from django.contrib.auth.models import User, Group
class GroupLeader(models.Model):
user=models.OneToOneField(User, primary_key=True)
groups=models.ManyToManyField(Group, related_name='leaders')
class Meta:
verbose_name_plural=u'Group Leaders'
The next snippet makes the group leaders available in django admin:
#-----------------------------------------
# admin.py of fooapp
# See http://stackoverflow.com/questions/2216974/django-modelform-for-many-to-many-fields
from django.contrib.auth.admin import GroupAdmin
from fooapp.models import GroupLeader
class MyGroupForm(forms.ModelForm):
class Meta:
model=Group
leaders = forms.ModelMultipleChoiceField(queryset=User.objects.all())
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
# We get the 'initial' keyword argument or initialize it
# as a dict if it didn't exist.
initial = kwargs.setdefault('initial', {})
# The widget for a ModelMultipleChoiceField expects
# a list of primary key for the selected data.
initial['leaders'] = [t.pk for t in kwargs['instance'].leaders.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
self.fields['leaders'].widget.attrs['size']=10
def save(self, commit=True):
instance = forms.ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form,
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
instance.leaders.clear()
for leader_user in self.cleaned_data['leaders']:
leader_obj, created = GroupLeader.objects.get_or_create(pk=leader_user.pk) # User object to GroupLeader object
instance.leaders.add(leader_obj)
self.save_m2m = save_m2m
# Do we need to save all changes now?
if commit:
instance.save()
self.save_m2m()
return instance
class MyGroupAdmin(GroupAdmin):
form=MyGroupForm
admin.site.unregister(Group)
admin.site.register(Group, MyGroupAdmin)
In the default Django admin view for user-object (edit user) one can edit the user's group memberships. What if I wanted this the other way around also? I.e. in the group editing page one could select the users that belong to the group being edited.
As I see this, Django doesn't have a ManyToMany mapping from Group to User object which makes it impossible(?) to implement a ModelAdmin class for this particular case. If I could make an additional UsersOfGroup model class and use it in the Django's Group model's ManyToMany field as a through-attribute, there could be a way.
Any ideas, is this possible to implement using ModelAdmin tricks or do I just have to make a custom view for editing groups?
I have checked these two other questions, but they don't quite do the same thing:
Assigning a group while adding user in admin
and
Show group membership in admin
Updated:
The answer from Chris was almost there. :) The group has a reference to the users set, but it's called user_set, not users. So these are the changes I made:
if self.instance and self.instance.pk:
self.fields['users'].initial = self.instance.user_set.all()
and
if group.pk:
group.user_set = self.cleaned_data['users']
The save method above won't work if you add a new group and simultaneously add users to the group. The problem is that the new group won't get saved (the admin uses commit=False) and won't have a primary key. Since the purpose of save_m2m() is to allow the calling view to handle saving m2m objects, I made a save object that wraps the old save_m2m method in a new method.
def save(self, commit=True):
group = super(GroupAdminForm, self).save(commit=commit)
if commit:
group.user_set = self.cleaned_data['users']
else:
old_save_m2m = self.save_m2m
def new_save_m2m():
old_save_m2m()
group.user_set = self.cleaned_data['users']
self.save_m2m = new_save_m2m
return group
yourapp/admin.py
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import User, Group
class GroupAdminForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name=_('Users'),
is_stacked=False
)
)
class Meta:
model = Group
def __init__(self, *args, **kwargs):
super(GroupAdminForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
self.fields['users'].initial = self.instance.users.all()
def save(self, commit=True):
group = super(GroupAdminForm, self).save(commit=False)
if commit:
group.save()
if group.pk:
group.users = self.cleaned_data['users']
self.save_m2m()
return group
class GroupAdmin(admin.ModelAdmin):
form = GroupAdminForm
admin.site.unregister(Group)
admin.site.register(Group, GroupAdmin)
I am on Django 2.1 and was using the solution posted by Chris, but like explained by Cedric, it wouldn't work when when a new group was added and simultaneously users were added to the new group. Unfortunately, his code didn't help either, but I could get it to work using this modified version below.
Edit: I included the suggestion of user am70 (thanks!) and made use of get_user_model(). This code continues to work with Django 3.1 and has been tested with Python 3.6, 3.7 and 3.8.
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth import get_user_model ; User = get_user_model()
from django.contrib.auth.models import Group
class GroupAdminForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name=_('Users'),
is_stacked=False
)
)
class Meta:
model = Group
def __init__(self, *args, **kwargs):
super(GroupAdminForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
self.fields['users'].initial = self.instance.users.all()
class GroupAdmin(admin.ModelAdmin):
form = GroupAdminForm
def save_model(self, request, obj, form, change):
super(GroupAdmin, self).save_model(request, obj, form, change)
if 'users' in form.cleaned_data:
form.instance.user_set.set(form.cleaned_data['users'])
admin.site.unregister(Group)
admin.site.register(Group, GroupAdmin)
Here is a simpler approach that uses Django's InlineModelAdmin objects (answered here on Qubanshi.cc)
from django.contrib.auth.admin import GroupAdmin
from django.contrib.auth.models import User, Group
class UserSetInline(admin.TabularInline):
model = User.groups.through
raw_id_fields = ('user',) # optional, if you have too many users
class MyGroupAdmin(GroupAdmin):
inlines = [UserSetInline]
# unregister and register again
admin.site.unregister(Group)
admin.site.register(Group, MyGroupAdmin)