I'm trying to use a property in my model as a field in django admin (1.2).
Here's an example of my code:
class Case(models.Model):
reference = models.CharField(_(u'Reference'), max_length=70)
client_read = models.BooleanField(default=0)
def __unicode__(self):
return self.case_id
#property
def case_id(self):
""" unique case ID """
number = (settings.CASE_ID_LENGTH - len(str(self.id))) * "0" + str(self.id)
return '%(year)s%(unique_id)s' % {
'year': self.case_date.strftime("%y"),
'month': self.case_date.strftime("%m"),
'unique_id': number}
and the part of admin.py:
class OrderAdmin(ReadOnlyAdminFields, admin.ModelAdmin):
[...]
search_fields = ('id','case','req_surname','req_forename','req_company')
I can refer to the field as 'case' (like given in the example), but this gives me a TypeError: Related Field has invalid lookup: icontains
Of course it's working the way with related fields: so I can use case__id and then I'm able to use the id as search query.
But this is somewhat irritating to the users cause the caseid is shown different.
Is there a way to use the case_id as search query like it's shown (year+month+id)?
No you cannot use it that way, because this only works with attributes that represent columns in the database, not with properties. The only way to make this work would be using a subclass of contrib.admin.views.main.ChangeList for the change list and overwrite it's get_query_set method to achieve the desired behaviour!
Related
I am using only the django admin , and trying to creating a custom filter, where is to filter the date of another model.
My models
class Avaria(models.Model):
.....
class Pavimentacao(models.Model):
avaria = models.ForeignKey(Avaria, related_name='AvariaObjects',on_delete=models.CASCADE)
date= models.DateField(blank=True,null=True)
AvariaAdmin
class AvariaAdmin(admin.ModelAdmin):
list_filter = ('')
For example, Let's say you have a model and you have to add custom ContentTypeFilter to your model admin then. you can define a class which inherit SimpleListFilter and define lookups and queryset based on your requirement and add this class to list_filter like
list_filter = [ContentTypeFilter]
Refer to docs
Example class definition is like below:
class ContentTypeFilter(admin.SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = _('content type')
# Parameter for the filter that will be used in the URL query.
parameter_name = 'type'
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
models_meta = [
(app.model._meta, app.model.__name__) for app in get_config()
]
return (item for item in models_meta)
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
if not self.value():
return
model = apps.get_model(self.value())
if model:
return queryset.models(model)
You have to add the field you want to filter. In your example if you want to filter on date you put list_filter('date'). Dont forget to register the model admin as seen here
I have a model currently defined like this:
class Category(models.Model):
ID = models.AutoField()
name = models.CharField()
desc = models.CharField()
Another model Subcategory has a ForeignKey defined on Category.
When I run:
Category._meta.get_fields()
I get:
(<ManyToOneRel: siteapp.subcategory>, <django.db.models.fields.AutoField: ID>, <django.db.models.fields.CharField: name>, <django.db.models.fields.CharField: desc>)
However, I don't want the ManyToOneRel fields; I just want the others.
Currently, I am doing something like this:
from django.db.models.fields.reverse_related import ManyToOneRel
field_list = []
for field in modelClass._meta.get_fields():
if not isinstance(field, ManyToOneRel):
field_list.append(field)
However, is there a better way to do this, with or without using the model _meta API?
You could use the concrete_fields property.
Category._meta.concrete_fields
However this is an internal Django API, and it may be better to use get_fields() with your own filtering, even though it may be a little more verbose.
I had the same issue creating a serializer mixin that treated only GenericRelaTion fields. Unfortunately, when you use the get_fields() the ManyToOneRel in somecases appear principally when you need to get attname of a field. Therefore I created a function that treates this issue skiping all the ManyToOneRel from fields:
def get_generic_relation_fields(self):
"""
This function returns all the GenericRelation
fields needed to return the values that are
related such as polymorphic models.
"""
other_field = [field for field in self.Meta.model._meta.get_fields()]
fields = [field.attname for field in self.Meta.model._meta.get_fields() if not isinstance(field, (ManyToOneRel))]
generic_relation_fields = []
for field in fields:
get_type = self.Meta.model._meta.get_field(field)
field_type = get_type.__class__.__name__
if field_type == "GenericRelation": #<----- change field name to filter different fields.
generic_relation_fields.append(field)
return generic_relation_fields
Usage:
class MyModel(models.Model):
. . .
first_name = GenericRelation(MyUserPolymorphic)
last_name = GenericRelation(MyUserPolymorphic)
whatever = GenericRelation(MyUserPolymorphic)
class MyModelSerializer(serializer.ModelSerializer)
def create(self, validated_data):
fields = [field for field in get_generic_relation_fields(self)]
print("FIELDS ---->", fields)
. . .
output on POST:
FIELDS ----> ['first_name', 'last_name', 'whatever']
However, there I added 'GenericRelation' field you can add other fields to be filtered and treated as you want.
I have been looking for a solution for this, and I've ended up writing my own script which while not the most clean pythonic code, works. I thought to put it here if someone stumbles upon it in the future.
[field for field in fields if str(type(field)) != "<class 'django.db.models.fields.related.ForeignKey'>"]
I have also used this script as follows to get a dict in the format of {field_name:field_value}
{
field.name: (
getattr(self, field.name)
if str(type(field))
!= "<class 'django.db.models.fields.related.ForeignKey'>"
else getattr(self, field.name).id
)
for field in fields
if str(type(field))
!= "<class 'django.db.models.fields.reverse_related.ManyToOneRel'>"}
I had to do an additional check for ForeignKey constraints as usually showing the field value didn't make sense in that case
I have a django project with a model that looks like:
class Profile(models.Model):
#some other stuff
owner = models.OneToOneField(settings.AUTH_USER_MODEL)
last_modified = models.DateTimeField(default = timezone.now)
def __unicode__(self):
return self.owner.name
__unicode__.admin_order_field = 'owner__last_name'
My model admin looks something like:
class ProfileAdmin(admin.ModelAdmin):
ordering = ['-last_modified']
list_display = ['__unicode__', 'last_modified']
I would like for the admin to be sorted by last_modified by default (as it is now) but to be able to sort alphabetically by clicking on the top of the first column of the list display. I tried to add the __unicode__.admin_order_field line as described here, but that doesn't seem to have made any difference. Is what I want possible? If not why not?
You can only sort fields in the django admin interface if they are fields on your model or if they are fields you custom annotate in the get_queryset method of your ModelAdmin class--essentially fields created at the DB level. However, assuming you are deriving your __unicode__ or __str__ method from some fields on your model (and you are--from owner.name) you should be able to reference those fields and make the column sortable like so (though you could use this method to make the unicode field sortable on any model attribute you'd like):
class ProfileAdmin(admin.ModelAdmin):
def sortable_unicode(self, obj):
return obj.__unicode__()
sortable_unicode.short_description = 'Owner Name'
sortable_unicode.admin_order_field = 'owner__last_name'
ordering = ['-last_modified']
list_display = ['sortable_unicode', 'last_modified']
Though I do find it a bit strange that you will be displaying the owner's name but sorting on last_name. This might be a bit puzzling when you wonder why your sort order doesn't match the displayed name in the admin interface.
Using django 1.7.7 admin page, I want to list the data in a table where I can sort and filter.
That's exactly what the picture on the documentation shows:
https://docs.djangoproject.com/en/1.7/ref/contrib/admin/
under the list_filter section.
I tried that and got an error:
The value of 'list_display[0]' refers to 'book_type', which is not a callable.
Then, I tried this:
# model.py:
class Interp():
""" specifies the interpretation model """
BOOK_TYPES = ['drama', 'scietific']
BOOK_TYPES = tuple(zip(BOOK_TYPES, BOOK_TYPES))
comment = models.CharField(max_length=20)
book_type = models.CharField(max_length=20, choices=BOOK_TYPES,
default='not specified')
def __unicode__(self):
return self.book_type
# admin.py:
class bookTypeFilter(SimpleListFilter):
title = 'book type'
parameter_name = 'book_type'
def lookups(self, request, model_admin):
types = set([t.book_type for t in model_admin.model.objects.all()])
return zip(types, types)
def queryset(self, request, queryset):
if self.value():
return queryset.filter(book_type__id__exact=self.value())
else:
return queryset
class AdminInterpStore(admin.ModelAdmin):
""" admin page setting """
search_fields = ('comment', 'book_type')
list_display = ('book_type',)
list_filter = (bookTypeFilter, )
this shows a sidebar and lists the values but as soon as I click any of them, I get an error:
FieldError: Unsupported lookup 'id' for CharField or join on the field not permitted
How can I make a filter, preferably a table view as well?
Is the a complete sample for django1.7.7 that I can look at for filtering admin page?
You must replace
return queryset.filter(book_type__id__exact=self.value())
by
return queryset.filter(book_type=self.value())
See https://docs.djangoproject.com/en/1.8/ref/models/querysets/#field-lookups for more information.
Also, in the lookups method, using:
types = model_admin.model.objects.distinct('book_type').values_list('book_type', flat=True)
would be much more efficient as you would not retrieve the whole data from the database to then filter it.
Edit:
I realized that the book_type field is a choice field.
First, the value passed to the choices argument must be
An iterable (e.g., a list or tuple) consisting itself of iterables of exactly two items (e.g. [(A, B), (A, B) ...] A being the value stored in the database, B a human readable value)
So passing a list of strings will fail.
Second, the Django admin's default behaviour for such fields is exactly what you are trying to achieve so setting list_filter = ('book_type', ) in AdminInterpStore is enough.
I have the models set up like this:
class ParentModel(models.Model):
some_col = models.IntegerField()
some_other = models.CharField()
class ChildModel(models.Model)
parent = models.ForeignKey(ParentModel, related_name='children')
class ToyModel(models.Model)
child_owner = models.ForeignKey(ChildModel, related_name='toys')
Now in my admin panel when I open the changelist for ParentModel I want a new field/column in the list_display with a link to open the changelist of the ChildModel but with an applied filter to show only the children from the selected parent. For now I realized it with this method, but I think there is a cleaner way to do it, I just don't know how:
class ParentAdmin(admin.ModelAdmin)
list_display = ('id', 'some_col', 'some_other', 'list_children')
def list_children(self, obj):
url = urlresolvers.reverse('admin:appname_childmodel_changelist')
return 'List children'.format(url, obj.id)
list_children.allow_tags = True
list_children.short_description = 'Children'
admin.site.register(Parent, ParentAdmin)
So my question is, is it possible to achieve the same without this "link hacking"?
Also is it possible to indicate in a separate column in the ParentModel changelist, if any of its children has toys?
I think your approach to display the list_children column is correct. Don't worry about the 'link hacking', it's fine.
To display a column for indicate whether any of the object's children has toys, just define another method on the ParentAdmin class, and add it to list_display as before.
class ParentAdmin(admin.ModelAdmin):
list_display = ('id', 'some_col', 'some_other', 'list_children', 'children_has_toys')
...
def children_has_toys(self, obj):
"""
Returns 'yes' if any of the object's children has toys, otherwise 'no'
"""
return ToyModel.objects.filter(child_owner__parent=obj).exists()
children_has_toys.boolean = True
Setting boolean=True means Django will render the 'on' or 'off' icons as it does for boolean fields. Note that this approach requires one query per parent (i.e. O(n)). You'll have to test to see whether you get acceptable performance in production.