How can I show model information inside another model? in django - django

So, I have three models , and there are connected with foreign key.
I'm using list_display on "avaria" , and I want to show the name of the "pavimento" inside of the model "avaria". What is the best options to that?
class pavimento(models.Model):
pavimento_nome = models.CharField("Pavimento",max_length=200)
class avaria(models.Model):
avaria_consumidores = models.IntegerField(blank=True,null=True,verbose_name="Consumidores")
class pavimentacao(models.Model):
pavimentacao_id=models.ForeignKey(avaria,related_name='avariaObjects',on_delete=models.CASCADE)
pavimentacao_avaria = models.ForeignKey(pavimento,on_delete=models.CASCADE, verbose_name="Pavimento")

You can write a property for avaria
#property
def pavimento_names(self):
pavimentacaos = self.avariaObjects.select_related('pavimentacao_avaria').values_list('pavimentacao_avaria__pavimento_nome', flat=True)
return ', '.join(pavimentacaos)
And add 'pavimento_names' both to readonly_fields and list_display lists of avaria admin.
Or you can directly add method into your admin like:
class AvariaAdmin(admin.ModelAdmin):
list_display = (..., 'get_pavimento_names')
def get_pavimento_names(self, obj):
pavimentacaos = obj.avariaObjects.select_related('pavimentacao_avaria').values_list('pavimentacao_avaria__pavimento_nome', flat=True)
return ', '.join(pavimentacaos)
get_pavimento_names.short_description = 'Pavimento Names'
get_pavimento_names.admin_order_field = 'avariaObjects__pavimentacao_avaria__pavimento_nome'

Related

Caching reverse foreign key object in django admin display

I'm trying to fetch some different attribute from reverse foreign key object and show it on django admin list_display. But this current method will call the db queries multiple times
models.py:
class Author(models.Model):
name = models.CharField()
...
def get_all_book(self):
return self.book_set.all()
class Book(models.Model):
author = models.ForeignKey(Author)
aaa = some field type
bbb = some field type
...
admin.py:
class AuthorAdmin(admin.ModelAdmin):
def book_aaa(obj):
booklist = obj.get_all_book
all_bookaaa = ",".join([k.aaa for k in booklist])
return all_bookaaa
def book_bbb(obj):
booklist = obj.get_all_book
all_bookbbb = ",".join([k.bbb for k in booklist])
return all_bookbbb
list_display = ('name', book_aaa, book_bbb)
admin.site.register(Author, AuthorAdmin)
Because I need to separate those book information in separate column, but if using this method, it might called "book_set.all()" queryset twice, which is very bad for the performance. Is there any correct method to implement this problem?
By creating extra attribute to the object, and check whether the attribute exists or not.
def get_booklist(self, obj):
if not hasattr(obj, 'booklist')
obj.booklist = obj.get_all_book
return obj
def book_aaa(self, obj):
booklist = self.get_booklist(obj).booklist
all_bookaaa = ",".join([k.aaa for k in booklist])
return all_bookaaa
def book_bbb(self, obj):
booklist = self.get_booklist(obj).booklist
all_bookbbb = ",".join([k.bbb for k in booklist])
return all_bookbbb
list_display = ('name', 'book_aaa', 'book_bbb')
Maybe this is not the best solution, but at least can prevent the queryset called multiple times.

Django admin models

I have these models :
class Filters(models.Model):
def __unicode__(self):
return format('%s' % self.title)
title = models.CharField(max_length=255,verbose_name='Name filter')
class FilterValue(models.Model):
value = models.CharField(max_length=255,verbose_name='value filter')
filter = models.ForeignKey(Filters)
class Casino(models.Model):
title = models.CharField(max_length=255,verbose_name='Name Casino')
filters = models.ManyToManyField(Filters)
In admin
class AdminCasino(admin.ModelAdmin):
fields= ['title','filters']
How can I get view in admin multiselect:
Name Filter
--value filter of this name
and so on
I'm not sure I am completely understanding your question but it sounds like you want tabular inlines.
Try adding the following to your admin
class FilterValueInline(admin.TabularInline):
model = FilterValue
class AdminCasino(admin.ModelAdmin):
inlines = [FilterValueInline]

Show only some choices for ModelForm field

Suppose I have the following model
class MyChoiceModel(models.Model):
mychoices = (('ChoiceA', 'ChoiceA'), ('ChoiceB', 'ChoiceB'))
and the following ModelForm
class MyChoiceModelForm(forms.ModelForm):
#...
class Meta:
model = MyChoiceModel
fields = ('mychoices', )
Now, the user can select all types of choices (ChoiceA and ChoiceB).
What I want now is that certain choice values won't be displayed.
How can I filter the available choices from mychoices such that for example only ChoiceA would be selectable by the user and - under other circumstances - only ChoiceB?
There are numerous ways to do this: here is a way that i have
def CustomChoiceList():
# return custom choices
class MyChoiceModelForm(forms.ModelForm):
class Meta:
widgets = { 'mychoices': CustomChoiceList() }
if you need more control, or access to the model look at creating a forms.ModelChoiceField)
eg:
class CustomChoices(forms.ModelChoiceField):
def label_from_instance(self, obj):
# return some obj.title, or whatever in your object as the label to show
then in the ModelForm
mychoices = CustomChoices(required=True, queryset=YourModelYouWant.objects.filter(...))

Django REST Framework: adding additional field to ModelSerializer

I want to serialize a model, but want to include an additional field that requires doing some database lookups on the model instance to be serialized:
class FooSerializer(serializers.ModelSerializer):
my_field = ... # result of some database queries on the input Foo object
class Meta:
model = Foo
fields = ('id', 'name', 'myfield')
What is the right way to do this? I see that you can pass in extra "context" to the serializer, is the right answer to pass in the additional field in a context dictionary?
With that approach, the logic of getting the field I need would not be self-contained with the serializer definition, which is ideal since every serialized instance will need my_field. Elsewhere in the DRF serializers documentation it says "extra fields can correspond to any property or callable on the model". Are "extra fields" what I'm talking about?
Should I define a function in Foo's model definition that returns my_field value, and in the serializer I hook up my_field to that callable? What does that look like?
Happy to clarify the question if necessary.
I think SerializerMethodField is what you're looking for:
class FooSerializer(serializers.ModelSerializer):
my_field = serializers.SerializerMethodField('is_named_bar')
def is_named_bar(self, foo):
return foo.name == "bar"
class Meta:
model = Foo
fields = ('id', 'name', 'my_field')
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
You can change your model method to property and use it in serializer with this approach.
class Foo(models.Model):
. . .
#property
def my_field(self):
return stuff
. . .
class FooSerializer(ModelSerializer):
my_field = serializers.ReadOnlyField(source='my_field')
class Meta:
model = Foo
fields = ('my_field',)
Edit: With recent versions of rest framework (I tried 3.3.3), you don't need to change to property. Model method will just work fine.
With the last version of Django Rest Framework, you need to create a method in your model with the name of the field you want to add. No need for #property and source='field' raise an error.
class Foo(models.Model):
. . .
def foo(self):
return 'stuff'
. . .
class FooSerializer(ModelSerializer):
foo = serializers.ReadOnlyField()
class Meta:
model = Foo
fields = ('foo',)
if you want read and write on your extra field, you can use a new custom serializer, that extends serializers.Serializer, and use it like this
class ExtraFieldSerializer(serializers.Serializer):
def to_representation(self, instance):
# this would have the same as body as in a SerializerMethodField
return 'my logic here'
def to_internal_value(self, data):
# This must return a dictionary that will be used to
# update the caller's validation data, i.e. if the result
# produced should just be set back into the field that this
# serializer is set to, return the following:
return {
self.field_name: 'Any python object made with data: %s' % data
}
class MyModelSerializer(serializers.ModelSerializer):
my_extra_field = ExtraFieldSerializer(source='*')
class Meta:
model = MyModel
fields = ['id', 'my_extra_field']
i use this in related nested fields with some custom logic
My response to a similar question (here) might be useful.
If you have a Model Method defined in the following way:
class MyModel(models.Model):
...
def model_method(self):
return "some_calculated_result"
You can add the result of calling said method to your serializer like so:
class MyModelSerializer(serializers.ModelSerializer):
model_method_field = serializers.CharField(source='model_method')
p.s. Since the custom field isn't really a field in your model, you'll usually want to make it read-only, like so:
class Meta:
model = MyModel
read_only_fields = (
'model_method_field',
)
If you want to add field dynamically for each object u can use to_represention.
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ('id', 'name',)
def to_representation(self, instance):
representation = super().to_representation(instance)
if instance.name!='': #condition
representation['email']=instance.name+"#xyz.com"#adding key and value
representation['currency']=instance.task.profile.currency #adding key and value some other relation field
return representation
return representation
In this way you can add key and value for each obj dynamically
hope u like it
This worked for me.
If we want to just add an additional field in ModelSerializer, we can
do it like below, and also the field can be assigned some val after
some calculations of lookup. Or in some cases, if we want to send the
parameters in API response.
In model.py
class Foo(models.Model):
"""Model Foo"""
name = models.CharField(max_length=30, help_text="Customer Name")
In serializer.py
class FooSerializer(serializers.ModelSerializer):
retrieved_time = serializers.SerializerMethodField()
#classmethod
def get_retrieved_time(self, object):
"""getter method to add field retrieved_time"""
return None
class Meta:
model = Foo
fields = ('id', 'name', 'retrieved_time ')
Hope this could help someone.
class Demo(models.Model):
...
#property
def property_name(self):
...
If you want to use the same property name:
class DemoSerializer(serializers.ModelSerializer):
property_name = serializers.ReadOnlyField()
class Meta:
model = Product
fields = '__all__' # or you can choose your own fields
If you want to use different property name, just change this:
new_property_name = serializers.ReadOnlyField(source='property_name')
As Chemical Programer said in this comment, in latest DRF you can just do it like this:
class FooSerializer(serializers.ModelSerializer):
extra_field = serializers.SerializerMethodField()
def get_extra_field(self, foo_instance):
return foo_instance.a + foo_instance.b
class Meta:
model = Foo
fields = ('extra_field', ...)
DRF docs source
Even though, this is not what author has wanted, it still can be considered useful for people here:
If you are using .save() ModelSerializer's method, you can pass **kwargs into it. By this, you can save multiple dynamic values.
i.e. .save(**{'foo':'bar', 'lorem':'ipsum'})
Add the following in serializer class:
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['package_id'] = "custom value"
return representation

field choices() as queryset?

I need to make a form, which have 1 select and 1 text input. Select must be taken from database.
model looks like this:
class Province(models.Model):
name = models.CharField(max_length=30)
slug = models.SlugField(max_length=30)
def __unicode__(self):
return self.name
It's rows to this are added only by admin, but all users can see it in forms.
I want to make a ModelForm from that. I made something like this:
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=CHOICES),
}
but it doesn't work. The select tag is not displayed in html. What did I wrong?
UPDATE:
This solution works as I wanto it to work:
class ProvinceForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ProvinceForm, self).__init__(*args, **kwargs)
user_provinces = UserProvince.objects.select_related().filter(user__exact=self.instance.id).values_list('province')
self.fields['name'].queryset = Province.objects.exclude(id__in=user_provinces).only('id', 'name')
name = forms.ModelChoiceField(queryset=None, empty_label=None)
class Meta:
model = Province
fields = ('name',)
Read Maersu's answer for the method that just "works".
If you want to customize, know that choices takes a list of tuples, ie (('val','display_val'), (...), ...)
Choices doc:
An iterable (e.g., a list or tuple) of
2-tuples to use as choices for this
field.
from django.forms.widgets import Select
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=( (x.id, x.name) for x in CHOICES )),
}
ModelForm covers all your needs (Also check the Conversion List)
Model:
class UserProvince(models.Model):
user = models.ForeignKey(User)
province = models.ForeignKey(Province)
Form:
class ProvinceForm(ModelForm):
class Meta:
model = UserProvince
fields = ('province',)
View:
if request.POST:
form = ProvinceForm(request.POST)
if form.is_valid():
obj = form.save(commit=True)
obj.user = request.user
obj.save()
else:
form = ProvinceForm()
If you need to use a query for your choices then you'll need to overwrite the __init__ method of your form.
Your first guess would probably be to save it as a variable before your list of fields but you shouldn't do that since you want your queries to be updated every time the form is accessed. You see, once you run the server the choices are generated and won't change until your next server restart. This means your query will be executed only once and forever hold your peace.
# Don't do this
class MyForm(forms.Form):
# Making the query
MYQUERY = User.objects.values_list('id', 'last_name')
myfield = forms.ChoiceField(choices=(*MYQUERY,))
class Meta:
fields = ('myfield',)
The solution here is to make use of the __init__ method which is called on every form load. This way the result of your query will always be updated.
# Do this instead
class MyForm(forms.Form):
class Meta:
fields = ('myfield',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the query here
MYQUERY = User.objects.values_list('id', 'last_name')
self.fields['myfield'] = forms.ChoiceField(choices=(*MYQUERY,))
Querying your database can be heavy if you have a lot of users so in the future I suggest some caching might be useful.
the two solutions given by maersu and Yuji 'Tomita' Tomita perfectly works, but there are cases when one cannot use ModelForm (django3 link), ie the form needs sources from several models / is a subclass of a ModelForm class and one want to add an extra field with choices from another model, etc.
ChoiceField is to my point of view a more generic way to answer the need.
The example below provides two choice fields from two models and a blank choice for each :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=([['','-'*10]]+[[x.id, x.__str__()] for x in Speakers.objects.all()]))
event = forms.ChoiceField(choices=( [['','-'*10]]+[[x.id, x.__str__()] for x in Events.objects.all()]))
If one does not need a blank field, or one does not need to use a function for the choice label but the model fields or a property it can be a bit more elegant, as eugene suggested :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=((x.id, x.__str__()) for x in Speakers.objects.all()))
event = forms.ChoiceField(choices=(Events.objects.values_list('id', 'name')))
using values_list() and a blank field :
event = forms.ChoiceField(choices=([['','-------------']] + list(Events.objects.values_list('id', 'name'))))
as a subclass of a ModelForm, using the one of the robos85 question :
class MixedForm(ProvinceForm):
speaker = ...