Django admin reverse foregin key relationship - python

I have a number of different models connected to the User model through a foregin key relationship. I would now like to display all the attributes from the objects connected to the User model in the main admin overview.
models.py
class User(AbstractBaseUser):
username = models.CharField(max_length=60)
lastname = models.CharField(max_length=60, blank=True)
class UserProfile(models.Model):
user_id = models.OneToOneField(
User,
on_delete=models.CASCADE,
primary_key=True,
)
address = models.CharField(max_length=60, blank=True)
admin.py
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('user_firstname', 'user_lastname', 'address')
def user_firstname(self, instance):
return instance.user_id.username
def user_lastname(self, instance):
return instance.user_id.lastname
admin.site.register(UserProfile, UserProfileAdmin)
The code above works perfectly well to display attributes from "User" in "Userprofile", but how do I do this the other way around? In my code I currently have 4 different objects connected to the User object so keen to find a way to display all the data there.

use inlines
models.py
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
admin.py
class BookInline(admin.TabularInline):
model = Book
class AuthorAdmin(admin.ModelAdmin):
inlines = [
BookInline,
]
more info read https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#inlinemodeladmin-objects

Related

Getting AttribueError by SlugRelatedField despite the object being saved

I am creating an API to save class teachers. Now all the fields in the ClassTeacher model are foreign fields so I am using a SlugRelatedField in the serializer. It looks like SlugRelatedField does not support attribute lookup like this "user__username" and raises attribute error HOWEVER the object is still being saved.
models.py
class ClassTeacher(models.Model):
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE)
class_name = models.ForeignKey(Classes, on_delete=models.CASCADE)
school_id = models.ForeignKey(School, on_delete=models.CASCADE)
serializers.py
class ClassTeacherSerializer(ModelSerializer):
teacher = SlugRelatedField(slug_field='user__username', queryset=Teacher.objects.all()) <---- this is causing the error
class_name = SlugRelatedField(slug_field='class_name', queryset=Classes.objects.all())
school_id = SlugRelatedField(slug_field='school_id__username', queryset=School.objects.all()) <---- and I am assuming that this will too
class Meta:
model = ClassTeacher
fields = '__all__'
I tried adding a #property in the Teacher model to retrieve the username and use the property in the slug_field but that did not work too.
How can I save the object without getting the error?
EDIT 1:
teachers/models.py
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
photo = models.URLField()
teacher/serializers.py
class TeacherSerializer(ModelSerializer):
class Meta:
model = Teacher
fields = '__all__'
school/models.py
class School(models.Model):
school_id = models.OneToOneField(User, on_delete=models.CASCADE)
principal = models.CharField(max_length=50)
name = models.CharField(max_length=50)
photo = models.URLField()
school/serializers.py
class SchoolSerializer(ModelSerializer):
class Meta:
model = School
fields = '__all__'
EDIT 2:
Here's how I used the #property by referring from here:
teacher/models.py
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
photo = models.URLField()
#Here's the extra property part
#property
def username(self):
return self.user.username
classteacher/serializers.py
class ClassTeacherSerializer(ModelSerializer):
#Here I changed user__username to just username as mentioned in the above link
teacher = SlugRelatedField(slug_field='username', queryset=Teacher.objects.all())
class_name = SlugRelatedField(slug_field='class_name', queryset=Classes.objects.all())
school_id = SlugRelatedField(slug_field='school_id__username', queryset=School.objects.all())
class Meta:
model = ClassTeacher
fields = '__all__'
try renaming serializer field from teacher to user and using slug_field='username'
You can use #property for example
class User(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
photo = models.URLField()
#property
def get_username(self):
return Teacher.objects.filter(user_id=self.id)
and than inside your ClassTeacherSerializer use slug_field='username'
let me know if it works.

Display a model field as readonly in Django admin

I want to display a model field ie module_name as read only in django user profile edit page. The model Category has a manytomany relationship with Profile model.
Category
id category_name module_name
1 A X
2 B Y
profile_category
id profile_id category_id
1 2 1
So for eg. when I am on the admin page to editing a user id of 2 then I would like to display the module name X if category id 1 is assigned to the user id 2
models.py
class Category(models.Model):
category_name = models.CharField(max_length=150, blank=False, null=True, default="", unique=True)
module_name = models.TextField(blank=False, null=True)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name ='profile')
phone_number = PhoneNumberField( blank=True, null=True)
organisation = models.CharField(max_length=30, blank=True)
category = models.ManyToManyField(Category)
admin.py
class ProfileInline(admin.StackedInline):
model = Profile
filter_horizontal = ('category',)
class CustomUserAdmin(UserAdmin):
inlines = (ProfileInline, )
list_select_related = ( 'profile', )
Please suggest if this is possible or not.
Any help/suggestion is highly appreciated. Thanks in advance.
Method 1
Use readonly_fields which is inherited from admin.BaseModelAdmin.
class ProfileInline(admin.StackedInline):
model = Profile
readonly_fields = ('module_names',)
def module_names(self, instance):
# Write a get-method for a list of module names in the class Profile
# return HTML string which will be display in the form
return format_html_join(
mark_safe('<br/>'),
'{}',
((line,) for line in instance.get_module_names()),
) or mark_safe("<span>The user belongs to no category</span>")
# short_description functions like a model field's verbose_name
module_names.short_description = "Module names"
Method 2
For older versions of admin site, you can hook the get_fieldsets method of admin.StackedInline. This method was posted here with a nice example.

Dynamic choices in Foreignkey Field in Django Rest Framework

I have just started learning Django Rest Framework and trying to make a simple API using Django rest Framework.
This is my models.py
from __future__ import unicode_literals
from django.db import models
class Student(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=150, blank=False)
student_id = models.CharField(max_length=20, primary_key=True)
father_name = models.CharField(max_length=150)
mother_name = models.CharField(max_length=150)
class Meta:
ordering = ('student_id',)
class Subject(models.Model):
created = models.DateTimeField(auto_now_add=True)
subject_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(max_length=150)
class Meta:
ordering = ('subject_id',)
class Result(models.Model):
created = models.DateTimeField(auto_now_add=True)
grade = models.DecimalField(max_digits=5, decimal_places=3, blank=False)
student_id = models.ForeignKey(Student, on_delete=models.CASCADE)
subject_id = models.ForeignKey(Subject, on_delete=models.CASCADE)
class Meta:
ordering = ('created',)
And this is my serializers.py
from rest_framework import serializers
from models import *
class StudentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Student
fields = ('student_id', 'name', 'father_name', 'mother_name')
class SubjectSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Subject
fields = ('subject_id', 'name')
class ResultSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Result
fields = ('grade', 'student_id', 'subject_id')
In my "Result" model, I have two foreign keys; student_id and subject_id. This is how it looks like:
My questions is, how can I show the "name" field in the drop down menu in stead of showing "Student Object" and "Subject Object"?
I have tried with
STUDENT_CHOICES = [(each.student_id, each.name) for each in Student.objects.all()]
SUBJECT_CHOICES = [(each.subject_id, each.name) for each in Subject.objects.all()]
in the model's "choices=" field but it didn't work out.
Thanks in advance.
I think you're looking for this part of the DRF documentation.
Basically, your Django model's own representation is used. So for example, in your Student model you can add __str__ method:
# this is for Python 3, use __unicode__ on Python 2
def __str__(self):
return self.name
Meta options documentation for Django is here, look for model methods.

Django admin InlineModels for manytomany fields

I have designed following models for my blog
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField(default='')
created_at = models.DateTimeField('created date', auto_now_add=True, auto_now=False)
updated_at = models.DateTimeField('updated date', auto_now_add=False, auto_now=True)
author = models.ForeignKey('Author', default='admin')
def __str__(self):
return self.title
class Author(models.Model):
name = models.CharField(max_length=150)
email = models.EmailField(blank=True)
bio = models.TextField()
def __str__(self):
return self.name
class Category(models.Model):
cat_name = models.CharField(max_length=200)
post = models.ManyToManyField('Post')
def __str__(self):
return self.cat_name
class Tag(models.Model):
tag_name = models.CharField(max_length=200)
post = models.ManyToManyField('Post')
def __str__(self):
return self.tag_name
and I am trying to register this model under django admin in such a way that. I can edit the Category, Tags and Authors from the Post page. but I am having hard time to accomplish this talk, I have written this code in admin.py file
from django.contrib import admin
from .models import Post, Author, Tag, Category
class AuthorInline(admin.TabularInline):
model= Author
class TagInline(admin.StackedInline):
model= Tag
class CategoryInline(admin.StackedInline):
model = Category
#admin.register(Post) #another method of registration admin.site.register(Post, PostAdmin)
class PostAdmin(admin.ModelAdmin):
#Show the following fields in this order
fields = ['body', 'title']
#show the following filelds for nice formattng
list_display = ['title', 'author', 'created_at']
#display based on the date hirerarchy
date_hierachy = 'created_at'
#embed the following child models in this parent models
inlines = [AuthorInline, TagInline, CategoryInline,]
#to exclude fields
exclude = ('author',)
When I run my server I got the errors like
ERRORS:
<class 'blogs.admin.AuthorInline'>: (admin.E202) 'blogs.Author' has no ForeignKey to 'blogs.Post'.
<class 'blogs.admin.CategoryInline'>: (admin.E202) 'blogs.Category' has no ForeignKey to 'blogs.Post'.
<class 'blogs.admin.TagInline'>: (admin.E202) 'blogs.Tag' has no ForeignKey to 'blogs.Post'.
when investigating the error, we cannot have StackedInline class if the models doesn't have foreign key, but How can I put the Tags, Category and Author rendered formm under the Post page in django admin,
For using AuthorInline, you ned a foreignkey field in you Author model
ex:
class Author(models.Model):
post = models.ForeignKey('Post')
This means one post may have multiple authors.
But here in your situation you have the correct model and fileds which have one author for one post, so you can remove AuthorInline.
And incase of Tag and Category, you are using many-to-many field, It will be good if you go through this documentation https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-models
You have to rewrite the CategoryInline and TagInline;
class TagInline(admin.StackedInline):
model= Tag.post.through
class CategoryInline(admin.StackedInline):
model = Category.post.through
This isn't what inlines are for, and you don't want them here.
Inlines are for the reverse relation: given an author, edit their details and enter all their books on the same page. Your foreign keys and many-to-many fields are best shown as simple widgets, which is what Django does by default; the author and category will be displayed as a dropdown allowing you to choose an item, and the tags will be displayed as a multi-select box.
You might also choose to register Book as an inline on the Author admin; that's up to you.
Finally I made, what I wanted, the main gist is to make the category, author and tags choosable from the post page, so to do that, we need to add all the fields in the post model, which is the modified model
from django.db import models
from django.utils import timezone
class Author(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(blank=True)
bio = models.TextField()
class Tag(models.Model):
tag_name = models.CharField(max_length=50)
class Category(models.Model):
cat_name = models.CharField(max_length=50)
class Post(models.Model):
'''post can have many categories
and categories can have many post
author can have many post but post
can have single author
post can have many tags, and tags
can have many posts'''
title = models.CharField('post title', max_length=200)
body = models.TextField(default='', null=True)
created_at = models.DateTimeField(auto_now_add=True, auto_now=False)
updated_at = models.DateTimeField(auto_now_add=False, auto_now=True)
author = models.ForeignKey(Author, verbose_name = "List of Author") #many to one relationship
def __str__(self):
return self.title
#Generally many to many fields should into that model which is going to be edited.
tags = models.ManyToManyField(Tag)
categories = models.ManyToManyField(Category)
class Meta:
ordering = ['-created_at']
verbose_name_plural = "Posteeees"
# def post_status(self):
# return timezone.now() - self.updated_at <= 1
#Recursive realation, we can define the foreignkey itself to the model and this is called rrecursive realation
#

How to make two Django models with cross relationship?

I need two Django models: first with Users, second with Projects.
Between them I need many-to-many relationship with an additional field(s).
How to make the below code working?
from django.db import models
class User(models.Model):
name = models.CharField('Name', max_length=50)
projects = models.ManyToManyField(Project, through='UserProjects')
def __str__(self):
return self.name
class Project(models.Model):
name = models.CharField('Name', max_length=50)
users = models.ManyToManyField(User, through='UserProjects')
def __str__(self):
return self.name
class UserProjects(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey(Project)
is_active = models.BooleanField('Active')
At the end User.projects should return Projects for specified User
and in the same way Project.users should return Users for specified Project.
There's no need to put the m2m field on both sides
Jussi pick one, and Django will automatically create a reverse relationship for the other direction.
from django.db import models
class User(models.Model):
name = models.CharField('Name', max_length=50)
def __str__(self):
return self.name
class Project(models.Model):
name = models.CharField('Name', max_length=50)
users = models.ManyToManyField(User, through='UserProjects', related_name='projects')
def __str__(self):
return self.name
class UserProjects(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey(Project)
is_active = models.BooleanField('Active')
Here is the simplest solution in my opinion:
from django.db import models
class User(models.Model):
name = models.CharField('Name', max_length=50)
class Project(models.Model):
name = models.CharField('Name', max_length=50)
class UserProjects(models.Model):
user = models.ForeignKey(User, related_name='projects')
project = models.ForeignKey(Project, related_name='users')
is_active = models.BooleanField('Active')
In above User.projects returns Projects for specified User and also Project.users returns Users for specified Project.

Categories