Django Admin Inlines With OneToOne Field - python

Here in my model, you can see that I have a SpecializedProfile with a OneToOne relationship with a UserProfile, with a OneToOne relationship with the django user model.
I want to create an admin for the SpecializedProfile containing inlines for the UserProfile and the django User model, so that I can create a SpecializedProfile all at once, without needing to go to the UserProfile page and the User page.
Here is my model:
class UserProfile(models.Model):
user_auth = models.OneToOneField(User, related_name="profile", primary_key=True)
# more fields...
class SpecializedProfile(models.Model):
profile = models.OneToOneField(UserProfile, related_name="specialized_profile", primary_key=True)
# More fields...
and here is my attempt at creating the admin:
class UserInline(admin.TabularInline):
model = User
fk_name = 'profile__specialized_profile'
class ProfileInline(admin.TabularInline):
model = UserProfile
fk_name = 'specialized_profile'
class SpecializedProfileAdmin(admin.ModelAdmin):
model = SpecializedProfile
inlines = [
UserInline, ProfileInline
]
admin.site.register(SpecializedProfile, SpecializedProfileAdmin)
The admin isn't working, and I am getting this error:
<class 'profiles.admin.ProfileInline'>: (admin.E202) 'profiles.UserProfile' has no field named 'trainer'.
<class 'profiles.admin.UserInline'>: (admin.E202) 'auth.User' has no ForeignKey to 'profiles.SpecializedProfile'.
It seems like django wants the inlines to be on the models where the OneToOne fields are defined, and won't accept reverse relationships. I'd rather not have to go restructuring my models to make this work... is there anything I can do to make the inlines work with my model as-is?

I fixed that error by making reverse side inline, not from Profile to User but from overridden User to Profile:
class ProfileInline(admin.StackedInline):
model = Profile
class IspUserAdmin(UserAdmin):
list_display = ('username', 'first_name', 'last_name', 'email', 'is_staff', 'is_superuser', 'is_active')
list_filter = ('date_joined', 'last_login', 'is_staff', 'is_superuser', 'is_active',)
inlines = (ProfileInline,)
admin.site.unregister(User)
admin.site.register(User, IspUserAdmin)
Then I also tweaked Profile admin (removed Add action and changed some field links to custom ones).

There's a django module on github that will do this for you, without you having to reverse the relationships: django_reverse_admin.
Once installed, your admin would look like:
# admin.py
from django_reverse_admin import ReverseModelAdmin
class SpecializedProfileAdmin(ReverseModelAdmin):
model = SpecializedProfile
inline_reverse = ['profile']
inline_type = 'tabular' # could also be 'stacked'
admin.site.register(SpecializedProfile, SpecializedProfileAdmin)
Unfortunately, I don't think it can do nested inlines (Django can't either), so that would only solve part of your problem. I know you didn't want to change your database structure, but SpecializedProfile really seems more like a subclass of UserProfile. If you rewrote your model like so:
class SpecializedProfile(UserProfile):
# More fields...
Then you could have the admin like this:
# admin.py
from django_reverse_admin import ReverseModelAdmin
class UserProfileAdmin(ReverseModelAdmin):
model = UserProfile
inline_reverse = ['user_auth']
inline_type = 'tabular'
class SpecializedProfileAdmin(ReverseModelAdmin):
model = SpecializedProfile
inline_reverse = ['user_auth']
inline_type = 'tabular'
admin.site.register(SpecializedProfile, SpecializedProfileAdmin)
admin.site.register(UserProfile, UserProfileAdmin)
This way, you can view everything inline for both UserProfile and SpecializedProfile.

Related

IntegrityError at /admin/api/user/6/change/ FOREIGN KEY constraint failed

I am developing a website on django. When I am trying to delete a user via admin panel i get an error. I can change e.g. staff status (while still getting an error, but changes are getting apllied) The code is below:
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
emailSpam = models.BooleanField(default=True)
email = models.EmailField('email', unique=True)
first_name = None
last_name = None
confirmedEmail = models.BooleanField(default=False)
REQUIRED_FIELDS = ["emailSpam"]
forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import User
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = User
fields = ('email',)
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = User
fields = ('email',)
admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import User
class Admin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = User
list_display = ('email', 'is_staff', 'is_active',)
list_filter = ('email', 'is_staff', 'is_active',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Permissions', {'fields': ('is_staff', 'is_active')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')}
),
)
search_fields = ('email',)
ordering = ('email',)
admin.site.register(User, Admin)
Possible solutions
There are three things that might be causing the issue, at least as far as I can tell. The first you already discounted. I hope it's the second solution, since that will be easier, but I fear it might be the third, which would be hardest to get around.
Cause One
As my comment stated, perhaps there is another model with a field that has User as a ForeignKey with on_delete = models.CASCADE. When you try to delete the User, all instances of this class that has that ForeignKey will need to be deleted as well (because of on_delete=models.CASCADE), and that's what's causing the issue. You have already stated you have no such models, so let's move on to solution 2.
Cause Two
I hope it's this one, since it might be easier to fix. I noticed you have email = models.EmailField('email', unique=True) as one of your fields for your User model, but AbstractUser should already have an email field. Try removing that field, makemigrations and migrate, and see if the issue is resolved.
Cause Three
Did you change from the default User model to the custom user you are now using in mid-project? In other words, did you, in this project, ever run makemigrations before you switched to a custom user model? That can be a big problem. However, there are two solutions for this, not so easy or desirable, but doable.
Solution A: If this is a new project with no valuable data yet, you can simpy delete your database, delete all migrations in all folders, as well as all __pycache__ folders as described here. Then re do python manage.py makemigrations and python manage.py migrate. Of course this will erase all your tables, so you wouldn't want to do this mid-project.
Solution B: There is a way to handle this mid-project without losing data. The steps are out in this django ticket 25313, or these, more readable instructions.

Django admin save inline form instances with no fields

I'm attemping to add copies of a book through a books admin page.
Inside models.py I have:
class Book(models.Model):
...
class BookCopy(models.Model):
book = models.ForeignKey(
'Book',
related_name='copies',
on_delete=models.CASCADE
)
# No additional fields here
class BookCopyInline(admin.StackedInline):
model = BookCopy
can_delete = False
verbose_name_plural = 'copies'
Inside admin.py I have the following:
class BookCopyInline(admin.StackedInline):
model = BookCopy
can_delete = False
verbose_name_plural = 'copies'
#admin.register(Book)
class BookAdmin(admin.ModelAdmin):
inlines = (BookCopyInline,)
#admin.register(Book)
class BookAdmin(admin.ModelAdmin):
model = Book
list_display = ('isbn', 'title', 'subtitle')
prepopulated_fields = {'slug': ('title',)}
inlines = (BookCopyInline,)
I'd like to be able to add book copies inside the Books Admin page. But since the BookCopy model defines no additional fields the instances are never saved.
Adding a field to BookCopy and filling that in each time allows BookCopies to be created as normal, but I don't require any additional fields right now.
The image below demonstrates the issue I'm facing, new rows can be added, but when save is clicked, no BookCopies are created
Is there a way to have the admin save the instances regardless?

How to change djangos default User Creation Form

I extended the Django User Model and added a required ForeignKeyField called company. Now I also need to Change the default user creation Form. What I tried so far was:
Creating a new Form that Inherits from the default UserCreationForm form:
class MyUserCreationForm(UserCreationForm):
company = forms.ModelChoiceField(queryset=Company.objects.all())
class Meta:
model = MyUser
fields = ('username', 'company')
Adding it to my extended Django Admin:
class ExtendedUserAdmin(UserAdmin):
add_form = MyUserCreationForm
...
admin.site.register(MyUser, ExtendedUserAdmin)
This does not work. What am I missing?
Turns out you don't need to change add_form. All you need to do is add the following to ExtendedUserAdmin:
def __init__(self, model, admin_site):
super().__init__(model, admin_site)
self.add_fieldsets[0][1]['fields'] = ('username', 'password1', 'password2', 'company')

django two ForeignKeys to same model - admin error

I have a Profiles app that has a model called profile, i use that model to extend the django built in user model without subclassing it.
models.py
class BaseProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='owner',primary_key=True)
supervisor = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='supervisor', null=True, blank=True)
#python_2_unicode_compatible
class Profile(BaseProfile):
def __str__(self):
return "{}'s profile". format(self.user)
admin.py
class UserProfileInline(admin.StackedInline):
model = Profile
class NewUserAdmin(NamedUserAdmin):
inlines = [UserProfileInline ]
admin.site.unregister(User)
admin.site.register(User, NewUserAdmin)
admin
the error is
<class 'profiles.admin.UserProfileInline'>: (admin.E202) 'profiles.Profile' has more than one ForeignKey to 'authtools.User'.
obviously i want to select a user to be a supervisor to another user. I think the relationship in the model is OK, the one that's complaining is admins.py file. Any idea ?
You need to use multiple inline admin.
When you have a model with multiple ForeignKeys to the same parent model, you'll need specify the fk_name attribute in your inline admin:
class UserProfileInline(admin.StackedInline):
model = Profile
fk_name = "user"
class SupervisorProfileInline(admin.StackedInline):
model = Profile
fk_name = "supervisor"
class NewUserAdmin(NamedUserAdmin):
inlines = [UserProfileInline, SupervisorProfileInline]
Django has some documentation on dealing with this: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/#working-with-a-model-with-two-or-more-foreign-keys-to-the-same-parent-model
Here is an example that I have just tested to be working
class Task(models.Model):
owner = models.ForeignKey(User, related_name='task_owner')
assignee = models.ForeignKey(User, related_name='task_assigned_to')
In admin.py
class TaskInLine(admin.TabularInLine):
model = User
#admin.register(Task)
class MyModelAdmin(admin.ModelAdmin):
list_display = ['owner', 'assignee']
inlines = [TaskInLine]

Django 1.4 - extend user model and admin interface

Let's have 2 models that extends the user model called Ext1 and Ext2 declared as follow:
class ExtN(models.Model):
user = models.OneToOneField(User)
extra_param = models.xxxField()
then I declare in application specific admin.py file something like:
class ExtNInline(admin.StackedInline):
model = ExtN
can_delete = False
in which file do I need to put the following code, in order to see the form to edit both models related to the user?
class UserAdmin(UserAdmin):
inlines = (Ext1Inline, Ext2Inline, )
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Thanks in advance.
You should put it in the same admin.py file
Extending the user model

Categories