I have two one-to-one related models in Django. Simplified version as follows:
class User(models.Model):
name = models.CharField(max_length=40)
class Profile(models.Model):
age = models.IntegerField()
user = models.OneToOneField(User, on_delete=models.CASCADE)
I have used signals to automatically create a profile automatically when a new user is created.
I have also created a ProfileInline and included it in my UserAdmin as follows:
class ProfileInline(admin.TabularInline):
model = Profile
can_delete = False
class UserAdmin(admin.ModelAdmin):
list_display = ('name',)
inlines = [ProfileInline,]
The problem I am facing is that when a new User is being created in the admin module, and the person creating the new user also populates the field for the inline on the same page, an error is generated while saving because both the signals module and the admin are trying to create a new Profile.
Is there any way to prevent the ProfileInline from showing in the UserAdmin when adding a new User?
Thanks.
Add extra=0 in your ProfileInline to prevent create new profile instance when you create new User at admin side...
class ProfileInline(admin.TabularInline):
model = Profile
can_delete = False
extra = 0
class UserAdmin(admin.ModelAdmin):
list_display = ('name',)
inlines = [ProfileInline,]
OR You can use ModelAdmin get_inline_instances function. Following code removes inlines from add_view:
class ProfileInline(admin.TabularInline):
model = Profile
can_delete = False
class UserAdmin(admin.ModelAdmin):
list_display = ('name',)
inlines = [ProfileInline,]
def get_inline_instances(self, request, obj=None):
return obj and super(UserAdmin, self).get_inline_instances(request, obj) or []
Related
DRF docs says that "By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be save."<<
In general this is true except for one case. When User class is a nested object in serializer.
Use case is to add existing user to newly created organization.
Consider my simple example where I noticed that issue:
views.py
class UserOrganizationViewSet(viewsets.ModelViewSet):
# authentication_classes = (JwtAuthentication, SessionAuthentication)
# permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,)
serializer_class = UserOrganizationSerializer
queryset = UserOrganization.objects.all()
models.py:
class UserOrganization(models.Model):
name = models.CharField(max_length=256)
users = models.ManyToManyField(User, blank=True, related_name="user_organizations", through='OrganizationMember')
def __str__(self):
return self.name
class OrganizationMember(models.Model):
organization = models.ForeignKey(UserOrganization, on_delete=CASCADE)
user = models.ForeignKey(User, on_delete=CASCADE)
joined = AutoCreatedField(_('joined'))
serializers.py:
First version - User class has its own simple serializer and it is used as nested serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username']
class UserOrganizationSerializer(serializers.ModelSerializer):
users = UserSerializer(many=True, required=False)
class Meta:
model = UserOrganization
fields = ['id', 'name', 'users']
read_only_fields = ['id']
Is this case I am able to fetch data via GET method (Organization created through admin panel):
But when I am trying to create organization I am getting error which says that user already exist:
So, I checked what will happen when I try to paste not existed username in JSON data send. According to expectation DRF says that I need to override create() method:
{
"name": "Test2",
"users": [
{
"username": "admins"
}
]
}
AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `organization.serializers.UserOrganizationSerializer`, or set `read_only=True` on nested serializer fields.
And there is a first weird behavior. DRF still tries to created User object:
My create method looks like this:
def create(self, validated_data):
print("validated_data", validated_data)
users = validated_data.pop('users', [])
user_organization = UserOrganization.objects.create(**validated_data)
print("users", users)
for user in users:
userSerializer = UserSerializer(user)
print("userSerializer.data", userSerializer.data)
username = userSerializer.data['username']
print("username", username)
user_organization.users.add(get_user_model().objects.get(username=username))
return user_organization
In this case my method isn't called at all.
In case when I passed user which does not exist, my create() method is called and exeption is thrown as it should be. (I know I am not handling errors, this is just sandbox):
{
"name": "Test2",
"users": [
{
"username": "admin1"
}
]
}
django.contrib.auth.models.User.DoesNotExist: User matching query does not exist.
New model class UserProfile and serializer for this class used as nested:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ['name']
class UserOrganizationSerializer(serializers.ModelSerializer):
profiles = UserProfileSerializer(many=True)
class Meta:
model = UserOrganization
fields = ['id', 'name', 'profiles']
read_only_fields = ['id']
def create(self, validated_data):
print("validated_data ", validated_data)
users = validated_data.pop('profiles', [])
user_organization = UserOrganization.objects.create(**validated_data)
print("create", users)
for user in users:
print("username = ", user['name'])
user_organization.profiles.add(UserProfile.objects.get(name=user['name']))
user_organization.save()
return user_organization
Small change was needed to change field users to profiles inside UserOrganization.
Creating organization with existed user profile (created by admin) via POST:
In this scenario there is no such problem as mentioned in first case.
Final solution using SlugReleatedField works as expected. No serializer used for User.
class UserOrganizationSerializer(serializers.ModelSerializer):
users = serializers.SlugRelatedField(many=True, queryset=get_user_model().objects.all(), slug_field='username')
class Meta:
model = UserOrganization
fields = ['id', 'name', 'users']
read_only_fields = ['id']
Above code does what I exactly need and is very simple. Here is small concern regarding performance when fetched all users from DB in this line:
queryset=get_user_model().objects.all()
Can someone explain why this happens what we could observe in scenario 1?
Sorry for this post being so long. I tried to shorten it as much I as can. I can put whole project in github if it will be needed for readability.
These are my models:
class Partner(models.Model):
name = models.CharField(max_length=200, verbose_name="Organisation name")
class ResearchActivity(models.Model):
title = models.CharField(max_length=200)
partner = models.ManyToManyField(ActivityPartner, blank=True)
I'd like, in the Django administration forms, to have a field in my Partner edit form representing the ResearchActivity linked to that Partner.
Can this be achieved by adding a field to my Partner model (say, naming it linked_partner) and then edit my admin.py like so:
#admin.register(ActivityPartner)
class ActivityPartnerAdmin(admin.ModelAdmin):
search_fields = ['academic',]
autocomplete_fields = ['partnership_type', 'relationship_type', 'academic_links']
def get_changeform_initial_data(self, request):
return {'live_contract': ResearchActivity.objects.all().filter(linked_partner__id=request.ResearchActivity.partner.id)}
?
I have just come across in the display() decorator, new from Django 3.2. With it, I can simply do:
#admin.register(ActivityPartner)
class ActivityPartnerAdmin(admin.ModelAdmin):
search_fields = ['academic',]
autocomplete_fields = ['partnership_type', 'relationship_type', 'academic_links',]
readonly_fields = ('get_ra',)
#admin.display(description='Live contract(s)')
def get_ra(self, obj):
return list(ResearchActivity.objects.filter(partner=obj.id))
to achieve what I want.
If I also wanted to edit those ManyToMany relations, I can use the inlines option:
class LiveContractsInline(admin.TabularInline):
model = ResearchActivity.partner.through
#admin.register(ActivityPartner)
class ActivityPartnerAdmin(admin.ModelAdmin):
inlines = [
LiveContractsInline,
]
I've an app that deals with multiple user types. I need a way for differentiating them in the admin site. Some code to illustrate.
First I created a User model class that inherits from AbstractUser
class User(AbstractUser):
is_partner = models.BooleanField(default=False)
is_client = models.BooleanField(default=False)
email = models.EmailField(unique=True)
Partners and Clients users has different data:
class ClientProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
...
class PartnerProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
...
Client profile is created through SignUp form I provide, after that, users can update their own profile. In the other hand, Partner profile is created by myself as admin, and I need to do it through django admin site.
So, how do I register two version of the same model? and provide different names for showing in the admin index?
What I did was to change just the queryset in the ModelAdmin class, and register it twice, one for Client and another for Partner but it raises me
django.contrib.admin.sites.AlreadyRegistered
class ClientProfileInline(admin.StackedInline):
model = ClientProfile
can_delete = False
verbose_name_plural = 'Client Profile'
fk_name = 'user'
class ClientUserAdmin(UserAdmin):
inlines = (ClientProfileInline, )
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(is_client=True)#HERE it is the flag for differentiating between Client and Partner
def get_inline_instances(self, request, obj=None):
if not obj:
return list()
return super(ClientUserAdmin, self).get_inline_instances(request, obj)
...as this with PartnerUserAdmin...
admin.site.register(User, PartnerUserAdmin)
admin.site.register(User, ClientUserAdmin)
I resolved it using Proxy models, like this
class PartnerUser(User):
class Meta:
proxy = True
verbose_name = 'Partner'
class ClientUser(User):
class Meta:
proxy = True
verbose_name = 'Client'
admin.site.register(PartnerUser, PartnerUserAdmin)
admin.site.register(ClientUser, ClientUserAdmin)
I have a search model which has a ForeignKey relation to User
class Searches(models.Model):
user = models.ForeignKey(User)
......
I have a UserProfile model which has a OnetoOne Relationship to User
class UserProfile(models.Model):
user = models.OneToOneField(User)
photo = models.ImageField(upload_to='profile_images', blank=True)
ispublic=models.NullBooleanField()
I have attached UserProfile in admin.py as follows:
class UserProfileInline(admin.StackedInline):
model = UserProfile
can_delete = False
class UserProfileAdmin(UserAdmin):
inlines=(UserProfileInline, )
list_filter = UserAdmin.list_filter + ('email',)
list_display=('username','email','first_name','last_name','isPublic')
admin.site.unregister(get_user_model())
admin.site.register(get_user_model(), UserProfileAdmin)
Now I do not see a separate UserProfile but is integrated into User, which is what I want.
I also want to have Search model to show up in User admin. But also seperately.
how can I register two (or more) Admins to User model?
Try just putting another Inline inside the UserProfileAdmin, that will then place the UserProfileInline and SearchesInline in the UserProfileAdmin, then put admin.site.register(Searches) in admin.py. Unless I misunderstand the question.
I have a django model named Event with a generic inline relation to Relationship, like this:
# models.py
class Person(models.Model):
...
class Role(models.Model):
...
class Event(models.Model):
...
class Relationship(models.Model):
person = models.ForeignKey(Person)
role = models.ForeignKey(Role)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey("content_type", "object_id")
# admin.py
class RelationshipInline(generic.GenericTabularInline):
model = Relationship
extra = 0
class EventAdmin(admin.ModelAdmin):
inlines = [RelationshipInline]
I'd like to find a way to edit the inlines not only from the event admin page, but from the people admin page too. SO far I have added the following code to display inlines in the people page too
class ReverseRelationshipInline(admin.TabularInline):
model = Relationship
class IndividualAdmin(admin.ModelAdmin):
inlines = [ReverseRelationshipInline]
But I get content_type and object_id fields in the form and it is not very informational for admin users since it is just references to primary keys. I would rather prefer to resolve and show the content_object(even if it is not editable, to know at list to what objects are people related to).
Any direction to recommend?
Thanks.
Your "ReverseRelationshipInline" has to be a GenericTabularInline, not a TabularInline. That's all :-)
UPDATE
I think I now understand what you're after, and my answer is:
You won't be able to edit the content object inline of Person, but you want to show it nicely, maybe even as a link to its change form.
Add a function to Relationship which returns such an HTML link, provide your own ModelForm to your inline and specify the fields you want, which now includes your new function value (read-only). Something like this (untested):
# models.py
from django.core import urlresolvers
class Relationship(models.Model):
...
def link_content_object_changeform(self):
obj = self.content_object
change_url = urlresolvers.reverse(
'admin:%s_%s_change' % (
obj._meta.app_label,
obj._meta.object_name.lower()
),
args=(obj.id,)
)
return u'%s' % (change_url, obj.__unicode__())
link_content_object_changeform.allow_tags = True
link_content_object_changeform.short_description = 'in relation to'
# admin.py
class ReverseRelationshipInlineForm(forms.ModelForm):
class Meta:
model = Relationship
fields = ('person', 'role', 'link_content_object_changeform')
readonly_fields = ('link_content_object_changeform',)
class ReverseRelationshipInline(admin.TabularInline):
model = Relationship
form = ReverseRelationshipInlineForm