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)
Related
I have three models, Person, Role and Student. Person is related to role with ManyToManyField and Student inherits from Person. The code:
class Person(models.Model):
#fields
class Role(models.Model):
ROLE_CHOICES = (
('AL', 'ALUMNO'),
#... more choices
)
person = models.ManyToManyField(Person)
role = models.CharField(
'Rol',
max_length = 2,
choices = ROLE_CHOICES
)
class Student(Person):
# fields
def save(self, *args, **kwargs):
super(Student, self).save(*args, **kwargs)
# what code should I put here to save role?
I want to, on saving a Student, automatically save a role for him/her ('AL'). Also it has to execute on create and not in update.
I've seen other posts addresing this, but it remains unclear to me how
to implement this.
As I understand, I can override the save method, but I'm not sure how exactly do this. I'm aware that post_save signal can also accomplish this, but I'm not sure how either.
Thanks.
You can't do this before student get it's pk by save method,because m2m relations is establish by your instance pk and pk is generate after your instance save to db.
two way to archieve:
First one:
new a signals.py file in your app:
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import *
#receiver(post_save, sender=Student)
def create_student(sender, instance=None, created=False, **kwargs):
if created:
role, is_created = Role.objects.get_or_create(name='AL')
role.person.add(instance)
and in your apps.py
class UserConfig(AppConfig):
name = 'user'
def ready(self):
import user.signals
replace User with your own app_label.
Second one:
after django 1.9,django has transaction tool allow you performing actions after commit.doc is here:
from django.db import transaction
class Student(Person):
.
.
.
def save(self, *args, **kwargs):
instance = super(Student, self).save(*args, **kwargs)
if not self.pk:
# do when create
transaction.on_commit(self.update_role)
return instance
def update_role(self):
# this will be call after instance save to db
role, is_created = Role.objects.get_or_create(name='AL')
role.person.add(self)
all code is untested.
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)
How can I populate the following tables with some default / initial data, every-time new user is created ? I know about this https://docs.djangoproject.com/en/dev/howto/initial-data/, but this works only when I create models. Here I want to insert some default entries when new user is created.
Additionally, when I create a new user how can I add him to a given group with a static group id automatically ?
models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User)
class Feed(models.Model):
url = models.URLField()
name = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
description = models.TextField(blank=True)
category = models.ForeignKey(Category)
user = models.ForeignKey(User)
views.py
def signup(request):
if request.method == 'POST':
form = UserCreationForm1(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect("/accounts/login")
else:
form = UserCreationForm1()
return render(request, "registration/signup.html", {
'form': form,
})
forms.py
class UserCreationForm1(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ('username', 'email')
Many thanks!
What you're looking for is Django's signals framework.
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User, Group
from my_app import Category, Feed
#receiver(post_save, sender=User)
def init_new_user(instance, created, raw, **kwargs):
# raw is set when model is created from loaddata.
if created and not raw:
instance.groups.add(
Group.objects.get(name='new-user-group'))
Category.objects.create(
name="Default", user=instance)
Feed.objects.create(
user = instance,
name = "%s's Feed" % instance.first_name,
....
)
REF: https://docs.djangoproject.com/en/1.5/topics/signals/
There are at least two ways you can handle populating additional models with data when creating a new user. This first one that comes to mind is a post_save signal:
from django.db.models.signals import post_save
from django.dispatch import receiver
from your_app.models import Category, Feed
#receiver([post_save, post_delete], sender=Coupon)
def add_user_data(sender, **kwargs):
user = kwargs.get('instance')
try:
category, category_created = Category.objects.get_or_create(user=user,
defaults={'name': 'Some Value', 'user': user})
try:
feed, feed_Created = Feed.objects.get_or_create(user=user,
category=category, defaults={'url': 'some-url',
'name': 'some-name', 'description': 'some-desc',
'category': category, 'user': user})
except MultipleObjectsReturned:
pass
except MultipleObjectsReturned:
pass
This code would execute any time an instance of User is saved, but only create the additional records if they don't already exist for that user.
Another way would be to override the save() method on a form for the User. If you also need this functionality in Django admin, you would need to un-register the built-in UserAdmin and register your own UserAdmin class to leverage the custom form.
EDIT I reformulated the question here
I want to create several custom user models extending django.contrib.auth.models.User, with the following features:
some specific fields
a nice admin, i.e., for each model, an admin form that I can customize easily (eg. show/hide some fields, both from the parent django.contrib.auth.models.User and the child model).
I almost managed to do it with the code below, but I still have an issue: the password is cleared every time I want to modify an instance of MyUser1 or MyUser2 from the admin.
Is it the best way to do it? If so, how can I fix this cleared password issue?
models.py
from django.db import models
from django.contrib.auth.models import User
class MyUser1(User):
#add more fields specific to MyUser1
class MyUser2(User):
#add more fields specific to MyUser2
admin.py
class MyUser1AdminForm(forms.ModelForm):
class Meta:
model = MyUser1
def __init__(self, *args, **kwargs):
super(MyUser1AdminForm, self).__init__(*args, **kwargs)
self.fields['password'].widget = forms.PasswordInput()
def save(self, commit=True):
user = super(MyUser1AdminForm, self).save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
class MyUser1Admin(admin.ModelAdmin):
form = MyUser1AdminForm
admin.site.register(MyUser1, MyUser1Admin)
# same for MyUser2
if you whant for user to enter pass - add new field for pass and check it before commit
class MyUser1AdminForm(forms.ModelForm):
check_pass = forms.CharField(label="Password",widget = forms.PasswordInput(), required=True)
class Meta:
model = MyUser1
exclude = ('password',)
def save(self, commit=True):
user = super(MyUser1AdminForm, self).save(commit=False)
if commit and user.check_password(self.cleaned_data["check_pass"]):
user.save()
return user
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)