I have two classes in models.py:
class ModelOne(models.Model):
field_one = models.CharField(max_length=100)
field_two = models.CharField(max_length=200)
field_three = models.CharField(max_length=300)
[...] #other fields
def __unicode__(self):
return self.field_one
class ModelTwo(models.Model):
relation_model_one = models.ForeignKey(ModelOne)
other_field = models.CharField(max_length=50)
[...]
def __unicode__(self):
return self.relation_model_one.field_one
And your administration in admin.py is this:
class ModelTwoInline(admin.StackedInline):
model = ModelTwo
extra = 0
class ModelOneAdmin(admin.ModelAdmin):
list_display = ('field_one', 'field_two', 'field_three',)
inlines = [ModelTwoInline]
My question is:
Can I display the fields of the ModelTwo in list_display of the ModelOne? (The same for list_filter and search_fields)
I need this because I have many subclasses related to the main class!
You can display anything you want in list_display by writing an instance method with the #property decorator, which then returns whatever you need, and including it in list_display. I don't think that works for list_filter though.
So, let's return to your ModelOne class:
class ModelOne(models.Model):
[...]
def __unicode__(self):
return self.field_one
#property
def model_two_other_field(self):
return ', '.join([m2.other_field for m2 in self.modeltwo_set.all()])
Then, in your ModelOneAdmin:
class ModelOneAdmin(admin.ModelAdmin):
list_display = ('field_one', 'field_two', 'field_three', 'model_two_other_field')
[...]
I would note, for the record, that when you do this, you'll be requiring a database hit for every ModelOne instance being displayed. So, if you're listing 50 instances, you're incurring the overhead of 50 separate queries (one for each call of self.modeltwo_set.all()). That doesn't necessarily mean you shouldn't do it -- it might be the right answer, depending on your situation. Just be aware that it could be quite an expensive operation.
You should use the related name of the fields or RelatedModel._meta.get_fields
class ModelOneAdmin(admin.ModelAdmin):
list_display = ('field_one', 'field_two', 'field_three')
inlines = [ModelTwoInline]
def get_list_display(self, request):
extra_fields = [
f.name for model in self.inlines
for f in ModelTwo._meta.get_fields(include_hidden=False)
]
return self.list_display + extra_fields
Related
I'm trying to build my own poll app and the models have a lot of relation with each other and I need to count how many objects are in relation with some other objects so I need these custom function for it
Here's the model with the custom functions:
class Poll(models.Model):
title = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_available = models.BooleanField(default=True)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
#property
def total_votes(self):
votes = 0
for o in self.option_set.all():
votes = votes + o.how_much_vote
return votes
#property
def users_involved(self):
list = []
for o in self.option_set.all():
for v in o.vote:
list.append(v.user)
return list
#property
def users_involved(self):
users = []
for o in self.option_set.all():
for v in o.who_voted:
users.append(v)
return users
#property
def total_votes_each_option(self):
dct = {}
for o in self.option_set.all():
dct[o.title]= o.how_much_vote
return dct
My question is how do you include all of those custom functions total_votes, users_involved, etc to my api? because right now my api looks something like this:
{
"id": 1,
"title": "Which is the best frontend framework?",
"is_active": true,
"is_available": true,
"date_created": "2021-07-13T14:08:17.709054Z"
}
which is expected but I want to know how do I add those extra value to make it look like this
{
"id": 1,
"title": "Which is the best frontend framework?",
"is_active": true,
"is_available": true,
"date_created": "2021-07-13T14:08:17.709054Z",
"total_votes": "some number",
"users_involved": [...users]
}
Edit: here's my serializer class
class PollSerializer(serializers.ModelSerializer):
class Meta:
model = Poll
fields = '__all__'
Thank you very much
Simple way we can use SerializerMethodField
from rest_framework import serializers
class PollSerializer(serializers.ModelSerializer):
total_votes = serializers.SerializerMethodField()
users_involved = serializers.SerializerMethodField()
class Meta:
model = Poll
fields = (
'id',
'title',
'is_active',
'is_available',
'date_created',
'total_votes',
'users_involved',
)
def get_total_votes(self, poll):
return poll.total_votes
def get_users_involved(self, poll)
return poll.users_involved
Please note that SerializerMethodField is read_only, cannot for write.
Also make sure your custom model functions well for loop processing, using select_related or prefetch_related.
This one should be easy, you can add addition fields in Serializers by mentioning the field_name and its type before class Meta: something like this.
Model Upload
class Upload(models.Model):
.
.
total_files = models.IntegerField(default=0,null=True,blank=True)
.
.
def add(self):
return (self.total_files + 2)
Serializer FileSerializer
class FileSerializer(serializers.ModelSerializer):
#This is the line you need to add
extra_add = serializers.IntegerField(source = 'add')
class Meta:
model = Upload
fields= ['id','name','uploaded','total_files','created_on','updated_on','extra_add']
Then mention your extra_add field name in fields[..,..,'extra_add'].
You can change the type of field while mentioning the field thereserializer.<field_type>(source='<method_name>').
Result of the Above field i added:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username')
class PollSerializer(serializers.ModelSerializer):
total_votes = serializers.IntegerField(read_only=True)
users_involved = UserSerializer(read_only=True, many=True)
class Meta:
model = Poll
fields = '__all__'
#staticmethod
def setup_eager_loading(queryset):
return queryset.prefetch_related('option_set')
Also note that you defined users_involved twice in your Pool class.
You can use PoolSerializer.setup_eager_loading function in your view/viewset for better db query performance but it's not necessary (I do it in viewset.get_queryset method like self.queryset = self.serializer_class.setup_eager_loading(self.queryset).
I am new to Django and still learning. I am looking to keep track of how many events I have under a test. My current model looks like
class Test(models.Model):
name = models.CharField(max_length=255)
description = models.CharField(max_length=255, blank=True)
num_of_events = models.IntegerField(default=0)
class Meta:
verbose_name = 'Test'
verbose_name_plural = 'Tests'
def __str__(self):
return self.name
class Event(models.Model):
name = models.CharField(max_length=255)
test = models.ForeignKey(Test,on_delete=models.CASCADE)
class Meta:
verbose_name = 'Event'
verbose_name_plural = 'Events'
def __str__(self):
return self.name
def save(self):
obj, created = Test.objects.update_or_create(name=self.test)
obj.num_of_events += 1
super().save()
def delete(self):
self.test.num_of_events -= 1
super().delete()
I thought I could just override the save() function but it does not update on the admin panel and still shows 0.
I am trying to figure out what I am doing wrong.
EDIT: admin.py
class TestAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'num_of_events')
fieldsets = [
(None, {'fields': ('name', 'description')})
]
class EventsAdmin(admin.ModelAdmin):
pass
class PropertyAdmin(admin.ModelAdmin):
list_display = ('name', 'property_type', 'expected_value')
admin.site.register(Test, TestAdmin)
admin.site.register(Event, EventsAdmin)
admin.site.register(Property, PropertyAdmin)
You forget to save the Test object. For example with:
class Event(models.Model):
# …
def save(self):
if self.test_id is not None:
obj = self.test
obj.num_of_events += 1
obj.save()
super().save()
def delete(self):
if self.test_id is not None:
self.test.num_of_events -= 1
self.test.save()
super().delete()
But regardless, storing the number of items is usually not a good idea. Say that you change the .test of a given Event, then you need to subtract from the old Test and add to the new Test. Furthermore ORM operations in bulk, like .update(..) circumvent .save() and signals, so it will be hard or impossible to keep this correct.
The point is that you do not need to store the number of Events. Indeed, you can simply obtain these with:
from django.db.models import Count
Test.objects.annotate(number_of_events=Count('event'))
The Test objects that arise from this queryset will have an extra attribute .number_of_events that contains the number of related Event ojbects.
I have a simple Model like this:
class Artist(models.Model):
class Meta:
verbose_name = "Artist"
verbose_name_plural = "Artists"
name = models.CharField(max_length=128, unique=True, blank=False)
def test_function(self):
return 'xyz'
And Admin:
class ArtistAdmin(admin.ModelAdmin):
list_display = ['name', 'test_function']
search_fields = ['name']
readonly_fields = []
Now in the list view, the function field is verbosed as TEST_FUNCTION:
In a normal field I would use the Field.verbose_name parameter.
How do I achieve that with the function field?
In object terms thinking, I would try to return a mocked CharField instead of a simple string. Would this work somehow?
As per the documentation, you can give the function an attribute called short_description:
class PersonAdmin(admin.ModelAdmin):
list_display = ('upper_case_name',)
def upper_case_name(self, obj):
return ("%s %s" % (obj.first_name, obj.last_name)).upper()
upper_case_name.short_description = 'Name'
If set, the Django Admin will show this description rather than the function name. Even better, change it into _('Name') to allow for different translations!
Models:
class Owner(models.Model):
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class SomeThing(models.Model):
own_id = models.IntegerField(unique=True)
description = models.CharField(max_length=255, blank=True)
owner = models.ForeignKey(Owner, blank=True, null=True)
def __unicode__(self):
return self.description
Serializers:
class OwnerNameField(serializers.RelatedField):
def to_internal_value(self, data):
pass
def to_representation(self, value):
return value.name
def get_queryset(self):
queryset = self.queryset
if isinstance(queryset, (QuerySet, Manager)):
queryset = queryset.all()
lista = [Owner(name="------------")]
lista.extend(queryset)
return lista
class OwnerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Owner
fields = ('name', 'id')
class ThingSerializer(serializers.ModelSerializer):
owner = OwnerNameField(queryset=Owner.objects.all())
class Meta:
model = SomeThing
fields = ('own_id', 'description', 'owner')
Basically it works as intended. But when i add some fields to Owner class i would like to see all these fields in output of ThingSerializer (and be able to parse them - string doesn't suit here). I could change field owner to owner = OwnerSerializer() which gives me what i need. But when i want to add SomeThing object (tested in API browser) i also need add new Owner object - and i don't want it, i want use existing Owner object. How can i achieve it?
Finally i got it. This question describes exactly my problem and provided answers work as a charm!
Suppose I have car - suv/bus models.
I'd like to list all cars in the django admin (with name attribute).
When user clicks one of the car, it would go to the appropriate detail page of either suv or bus model.
How do I create a such admin list page?
class Car:
name = models.CharField()
class Meta:
abstract=True
class Suv(Car):
pass
class Bus(Car):
pass
Not sure this is the best approach, but sharing my solution here.
First, create a Database View
create view [app_label_model_name] as
select id, name, 1 as car_type
from suv
union
select id, name, 2 as car_type
from bus
order by something;
then create non-manged model
class PostBaseView(models.Model):
# this would be CarBaseView, I'm copying my actual code
id = models.IntegerField()
raw_html = models.TextField(null=True)
created_at = models.DateTimeField(primary_key=True)
post_type = models.IntegerField()
class Meta:
managed = False
then, from admin page, change the links based on subclass types.
class ChangeList(ChangeListDefault):
def url_for_result(self, result):
# pk = getattr(result, self.pk_attname)
id = result.id
app_label = result.get_app_label()
model_name = result.get_model_name()
return reverse('admin:%s_%s_change' % (app_label,
model_name),
args=(quote(id),))
class PostBaseViewAdmin(admin.ModelAdmin):
list_display = ['__str__', 'post_type_str']
class Meta:
model = PostBaseView
def get_changelist(self, request, **kwargs):
"""
Returns the ChangeList class for use on the changelist page.
"""
return ChangeList
admin.site.register(PostBaseView, PostBaseViewAdmin)
volla you have a admin that shows multiple subclasses at one list.
eugene's answer took me to the right direction. Thank you.
I'm not aware if there is a better way either, but in my case this approach solved the problem.
I already had a base class (Project) with some generic fields (SportProject and others), and specific classes that extends Project with their fields, and I wanted to have a single list of project, but wanted to have specific edit form for each project type.
In app/admin.py file, I did:
from django.contrib.admin.views.main import ChangeList as ChangeListDefault
from django.urls import reverse
class ProjectChangeList(ChangeListDefault):
def url_for_result(self, result):
app_label = result._meta.app_label.lower()
model_name = result.children._meta.object_name.lower()
return reverse('admin:%s_%s_change' % (app_label, model_name), args=(result.id,))
class ProjectDataAdmin(admin.ModelAdmin):
#...
def get_changelist(self, request, **kwargs):
"""
Returns the ChangeList class for use on the changelist page.
"""
return ProjectChangeList
In my model Project, I have a children property that returns the children instance (SportProject for instance). Not sure if there is another way to have this structure in Django.
I also had all classes registered in django-admin (Project and all children, as SportProject), to have django-admin pages for those classes. Thus, I'm using django-modeladmin-reorder to hide undesired links on django-admin main page.
Hope it helps someone.
You need to make your Car not an abstract model, as you have to have some base table for your vehicles.
class Car:
name = models.CharField()
class Meta:
abstract = False
class Suv(Car):
pass
class Bus(Car):
pass
class CarAdmin(admin.ModelAdmin):
model = Car
But here the easy things end. The admin doesn't have a built-in solution for your task. I suggest you overriding at least the CarAdmin.get_form() and CarAdmin.get_queryset() methods, though I'm not sure they are the only ones you have to customize.
I think this answer is applicable. ModelAdmin needs to know foreign key entries will be readonly, specified in a tuple called readonly_fields.
Using the problem that brought me here and there, I have (models.py):
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
def __str__(self):
return self.choice_text
class Answer(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE, default = 1)
class Meta:
abstract = True
class Vote(Answer):
choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
def answer(self):
return self.choice
def __str__(self):
return self.choice.choice_text
And (admin.py):
class VoteAdmin(admin.ModelAdmin):
#list_display = ('Answer.question.question_text', 'Answer.User.user_id', 'Choice.choice_text')
readony_fields = ('question', 'user')
list_display = ('question', 'user', 'choice')
fieldsets = [
('Question', {'fields': ['question']}),
('User', {'fields': ['user']}),
('Vote', {'fields' : ['choice']}),
]
Hope this proves useful to future searchers.