Overriding list_display in Django admin with custom verbose name - python

I have overridden the list_display to show inline fields like this:
class opportunityAdmin(admin.ModelAdmin):
list_display = ('name', 'Contact', 'Phone', 'Address', 'discovery_date', 'status' , 'outcome')
search_fields = ['name', 'tags' , 'description']
#readonly_fields = ('discovery_date','close_date')
inlines = [account_contactInline, account_updateInline]
def Contact(self, obj):
return '<br/>'.join(c.account_poc for c in account_contact.objects.filter(opportunity=obj.id).order_by('id')[:1])
def Phone(self, obj):
return '<br/>'.join(c.account_phone for c in account_contact.objects.filter(opportunity=obj.id).order_by('id')[:1])
def Address(self, obj):
return '<br/>'.join(c.account_address for c in account_contact.objects.filter(opportunity=obj.id).order_by('id')[:1])
My question is, in Django admin, the display name of the inline fields header used the function name: Contact, Phone and Address respectively. Actually i wanna display those field header with custom text. I even wanna use Chinese to display them. What have i missed?

You would need to define the short_description attribute on your functions: https://docs.djangoproject.com/en/stable/ref/contrib/admin/actions/#writing-action-functions
For example:
Contact.short_description = 'foo'

Related

Django: list_display accepts field but not list_filter

I have the following admin class:
class AppointmentAdmin(admin.ModelAdmin):
list_display = ('branch', 'date', 'timeslot', 'sold_to', 'unit', 'VIN')
list_filter = ('branch', 'date', 'sold_to', 'unit', 'VIN')
def VIN(self, obj):
return obj.unit.vin
I get the following error:
<class 'catalog.admin.AppointmentAdmin'>: (admin.E116) The value of 'list_filter[4]' refers to 'VIN', which does not refer to a Field.
If I remove VIN from list_filter, it executes fine. The list_display will show me the field VIN.
What am I doing wrong?
List filter doesn’t support ModelAdmin methods.
To filter on the vin based on your VIN method you would need to have unit__vin in the list_filter.
https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter

How to set fields/model to readonly if one attribute is set to true in Django?

So, I got three classes:
class User(...):
#... other fields
name = models.CharField()
class Equipment(...):
#... other fields
inUse = models.Boolean(default=False)
class Ticket(...):
#... Other fields
user = models.ForeignKey(...)
equipment = models.ForeignKey(...)
ended = models.Boolean(default=False)
Now, when I create a new Ticket, the equipment "inUse" attribute changes to True, so I cannot give the same Equipment to more than one User. The thing is that when I set the Ticket to "ended" I'll still be able to change it to not "ended". I want not to be able to change that once I set the "ended" attribute of the Ticket to True.
PS: I'm overriding the method save to make changes in the Equipment when setting up a new Ticket or changing it to "ended".
admin.py
from django.contrib import admin
from .models import Equipamento, TipoEquipamento, Funcionario, Setor, Ticket
# Register your models here.
class EquipamentoAdmin(admin.ModelAdmin):
list_display = ['codigo', 'categoria', 'descricao', 'ativo', 'emUso']
search_fields = ['codigo', 'descricao']
class FuncionarioAdmin(admin.ModelAdmin):
list_display = ['nome', 'setor', 'email']
search_fields = ['codigo', 'nome']
class TicketAdmin(admin.ModelAdmin):
list_display = ['id', 'get_user_name', 'get_equipment_name', 'dataDevolucao', 'finalizado']
autocomplete_fields = ['usuario', 'equipamento']
def get_user_name(self, obj):
return obj.usuario.nome
def get_equipment_name(self, obj):
return obj.equipamento.descricao
get_user_name.short_description = 'Funcionario'
get_equipment_name.short_description = 'Equipamento'
dumb question, after a few minutes I realized I could set a field like "can
Change" in the model so that when I change Status the field will be set to false, and I will not be able to change that model anymore. Daaah, thanks anyways.

django-filter filter on annotated field

class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.all()
serializer_class = EventSerializer
def get_queryset(self):
return super().get_queryset().annotate(
is_active=ExpressionWrapper(
Q(start_date__lt=timezone.now()) & Q(end_date__gt=timezone.now()),
output_field=BooleanField()
),
)
search_fields = [
'name',
'short_desc',
'desc',
]
filterset_fields = [
'is_active',
]
I have this ViewSet that I want to filter on an annotated field, normally you can simply just filter on the annotation in django querysets, however the above combined with this serializer:
class EventSerializer(serializers.ModelSerializer):
is_active = serializers.SerializerMethodField()
#staticmethod
def get_is_active(obj):
return obj.is_active
class Meta:
model = Event
fields = [
'timestamp',
'id',
'name',
'short_desc',
'desc',
'start_date',
'end_date',
'is_active',
]
I haven't looked deep into the source code but I'd assume it would do a simple qs.filter for the fields in filterset_fields but I'm getting this beautiful error that fails to explain much(at least to me):
'Meta.fields' contains fields that are not defined on this FilterSet: is_active
Apparently you simply don't add the declared filters on the Meta.fields list. Literally inside the docs and I found out about it by reading the code.
Also, when adding an annotated field to the declared_fields AKA the filterset class body, add a label or django-filters can't produce field label properly and just goes with "invalid name" instead of you know, the field name. Who knows why.

Django REST Framework nested resource key "id" unaccessible

So I have the following Structure:
A ClientFile belongs to an Owner (class name = Contact).
I'm trying to create a Clientfile using the API. The request contains the following data:
{
name: "Hello!"
owner: {
id: 1,
first_name: "Charlie",
last_name: "Watson"
}
}
I created the serializer according to my structure. Hoping that this API call would create a clientfile with the name "Hello!" and Contact id 1 as the owner:
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = (
'id',
'first_name',
'last_name',
)
class ClientfileSerializer(serializers.ModelSerializer):
owner = ContactSerializer(read_only=False)
class Meta():
model = Clientfile
fields = (
'id',
'name',
'owner',
)
def create(self, validated_data):
owner = Contact.objects.get(pk=validated_data['owner']['id'])
I do get into the create method. However, the only field I need (['owner']['id']) is not accessible. If I do print ['owner']['first_name'] it does return 'Charlie'. But the ID for some reasons doesn't seem to be accessible...
Any reasons why this can be happening? Am i missing something? (I'm new to Django)
SOLUTION: Just found out that the reason why ID didn't show in the first place was because I had to declare it in the fields like so: Hope this helps.
class ContactSerializer(serializers.ModelSerializer):
id = serializers.IntegerField() # ← Here
class Meta:
model = Contact
fields = (
'id',
'first_name',
'last_name',
)
In Django REST Framework AutoField fields (those that are automatically generated) are defaulted to read-only. From the docs:
read_only
Set this to True to ensure that the field is used when
serializing a representation, but is not used when creating or
updating an instance during deserialization.
Defaults to False
You can see this by inspecting your serializer by printing the representation in your shell:
serializer = ClientfileSerializer()
print repr(serializer)
You can override this by setting read_only=False against the id field in the extra_kwargs:
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = (
'id',
'first_name',
'last_name',
)
extra_kwargs = {'id': {'read_only': False}}
class ClientfileSerializer(serializers.ModelSerializer):
owner = ContactSerializer(read_only=False)
class Meta():
model = Clientfile
fields = (
'id',
'name',
'owner',
)
extra_kwargs = {'id': {'read_only': False}}
Alright so I found a different approach that works.
I added an IntegerField serializer for the owner relation. I also had to set the owner relation to read_only=True.
This is the json I am sending via POST:
{
name: "Hello!"
owner_id: 1
}
This is my serializer:
class ClientfileSerializer(serializers.ModelSerializer):
owner_id = serializers.IntegerField()
owner = ContactSerializer(read_only=True)
class Meta():
model = Clientfile
fields = (
'id',
'owner_id',
'owner',
)
It seems less cool than the first way, but it does the job.
Plus I don't want to create a new owner, but just select one that is already in the database. So maybe it's more semantic to only have the ID and not the full set of information posted via Json.
You can try something like this:
class YourModelSerializer(serializers.ModelSerializer):
class Meta:
model = YourModel
fields = ('id', 'field1', 'field2')
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
Add instance key to values if `id` present in primitive dict
:param data:
"""
obj = super(YourModelSerializer, self).to_internal_value(data)
instance_id = data.get('id', None)
if instance_id:
obj['instance'] = YourModel.objects.get(id=instance_id)
return obj
Then in serializer validated data you should have "instance" key if request.data has "id" key.
Also You can add just "id" instead of full instance/object.
The top voted answer does solve the issue but it raises a new one as mentioned in the comments we can no longer create a new record as it will thrown ac exception saying is required. We can set id to required=False then id will be available in validated_data and it wont be required to set it manually
id = serializers.IntegerField(required=False) <- Like this
class Meta:
model = Details
fields = ('id', 'product_name', 'description', 'specification', 'make_model',
'brand', 'quantity',)

A way to show/hide a field on Django

I have this as my list display in Django admin
list_display = ('product', 'price', 'purchase_date', 'confirmed', 'get_po_number', 'notes')
in models.py:
class PurchaseOrder(models.Model):
notes = models.TextField( null=True, blank= True)
This is what it looks like here:
[1]: http://i.imgur.com/ZyKmpoF.png '
As you can see 'notes' could take up a lot of room, so is there a way I can view/hide that field with the click of a button?
Instead of doing a button, you can resize the field to become smaller.
class PurchaseAdmin(admin.ModelAdmin):
formfield_overrides = {
models.CharField: {'widget': TextInput(attrs={'size':'20'})},
models.TextField: {'widget': Textarea(attrs={'rows':4, 'cols':40})},
}
admin.site.register(PurchaseOrder, PurchaseAdmin)
If you really want to have another button, you can use your custom Inline class to define the fields:
class CustomInline(admin.TabularInline):
readonly_fields = [...'link',...]
# important part which define what "link" looks like
def link(self, instance):
url = # your link to display the note
return mark_safe(u'View Note".format(u=url))
And in your custom admin class, use this Inline class instead:
class PurchaseAdmin(admin.ModelAdmin):
inlines = [CustomInline]

Categories

Resources