Django admin sorting: sorts by integer annotation like it's string - django-admin

I need django(2.1.1) admin to sort changelist page of a model by a calculated value. This is what I have in the admin class:
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.annotate(
virtual_sale=ExpressionWrapper(Func(F('sales_total'), Value('virtual'), Value('count'),function='jsonb_extract_path_text'),output_field=IntegerField())
)
return qs
def virtual_sale(self, obj):
return obj.virtual_sale
virtual_sale.admin_order_field = 'virtual_sale'
FYI, the annotation is to extract an IntegerField "virtual_sale" from the JSONField "sales_total".
But when sorting, it is sorted in way as virtual_sale values are strings, I mean its is sorted this way: [0,1,11,2,330,5]
What is the problem and how can I fix it?

Finally, I understood that I should Cast the fields into IntegerField.

Related

How to display list_filter with count of related objects in django admin?

How can I display the count of related objects after each filter in list_filter in django admin?
class Application(TimeStampModel):
name = models.CharField(verbose_name='CI Name', max_length=100, unique=True)
description = models.TextField(blank=True, help_text="Business application")
class Server(TimeStampModel):
name = models.CharField(max_length=100, verbose_name='Server Name', unique=True)
company = models.CharField(max_length=3, choices=constants.COMPANIES.items())
online = models.BooleanField(default=True, blank=True, verbose_name='OnLine')
application_members = models.ManyToManyField('Application',through='Rolemembership',
through_fields = ('server', 'application'),
)
class Rolemembership(TimeStampModel):
server = models.ForeignKey(Server, on_delete = models.CASCADE)
application = models.ForeignKey(Application, on_delete = models.CASCADE)
name = models.CharField(verbose_name='Server Role', max_length=50, choices=constants.SERVER_ROLE.items())
roleversion = models.CharField(max_length=100, verbose_name='Version', blank=True)
Admin.py
#admin.register(Server)
class ServerAdmin(admin.ModelAdmin):
save_on_top = True
list_per_page = 30
list_max_show_all = 500
inlines = [ServerInLine]
list_filter = (
'region',
'rolemembership__name',
'online',
'company',
'location',
'updated_on',
)
i.e After each filter in list filter, I want to show the count of related objects.
Now it only shows the list of filter
i.e location filter list
Toronto
NY
Chicago
I want the filter to show the count like below:
Toronto(5)
NY(3)
Chicago(2)
And if the filter has 0 related objects, don't display the filter.
This is possible with a custom list filter by combining two ideas.
One: the lookups method lets you control the value used in the query string and the text displayed as filter text.
Two: you can inspect the data set when you build the list of filters. The docs at https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter shows examples for a start decade list filter (always shows «80s» and «90s») and a dynamic filter (shows «80s» if there are matching records, same for «90s»).
Also as a convenience, the ModelAdmin object is passed to the lookups
method, for example if you want to base the lookups on the available
data
This is a filter I wrote to filter data by language:
class BaseLanguageFilter(admin.SimpleListFilter):
title = _('language')
parameter_name = 'lang'
def lookups(self, request, model_admin):
# Inspect the existing data to return e.g. ('fr', 'français (11)')
# Note: the values and count are computed from the full data set,
# ignoring currently applied filters.
qs = model_admin.get_queryset(request)
for lang, name in settings.LANGUAGES:
count = qs.filter(language=lang).count()
if count:
yield (lang, f'{name} ({count})')
def queryset(self, request, queryset):
# Apply the filter selected, if any
lang = self.value()
if lang:
return queryset.filter(language=lang)
You can start from that and adapt it for your cities by replacing the part with settings.LANGUAGES with a queryset aggregation + values_list that will return the distinct values and counts for cities.
Éric's code got me 80% of what I needed. To address the comment he left in his code (about ignoring currently applied filters), I ended up using the following for my use case:
from django.db.models import Count
class CountAnnotatedFeedFilter(admin.SimpleListFilter):
title = 'feed'
parameter_name = 'feed'
def lookups(self, request, model_admin):
qs = model_admin.get_queryset(request).filter(**request.GET.dict())
for pk, name, count in qs.values_list('feed__feed_id', 'feed__feed_name').annotate(total=Count('feed')).order_by('-total'):
if count:
yield pk, f'{name} ({count})'
def queryset(self, request, queryset):
feed_id = self.value()
if feed_id:
return queryset.filter(feed_id=feed_id)
And then, in the admin model:
class FeedEntryAdmin(admin.ModelAdmin):
list_filter = (CountAnnotatedFeedFilter,)
Note: As Éric also mentioned, this can impact the speed of the admin panel quite heavily, as it may have to perform expensive queries.

Grails data binding to command object with maps - convert key to number

I have a Grails command object with a list of maps. The map key is intended to be a numeric domain object ID.
class MyCommand {
def grid = [].withDefault { [:] }
}
Data binding to the list/map is working in general because of the dynamic list expansion.
However, in the POST, the map keys are being bound as Strings and I want them to be Longs, as they are when the form is initially populated. I want foo[123] in my map, not foo['123'].
Alternatively I would be satisfied if the [] operators found the correct value given a numeric ID key to look up. In other words, if I could get foo[123] to return the same value as foo['123'], that would work too.
Any way to get this to work the way I want to? Maybe strongly type the map?
Or a better approach?
You can inject the property into the map to convert a String key to Long. For example:
def myMap = [:] << ['1': "name"] << ['Test': "bobo"]
def result = myMap.inject([:]){map, v ->
def newKey = v.key.isNumber() ? v.key.toLong() : v.key
map[newKey] = v.value
map
}
assert myMap['1'] == 'name'
assert result[1L] == 'name'
assert result['Test'] == 'bobo'

gorm projection and loss of metainformation

When using projection on the properties, the result is returned as the list with the elements in the same sequence as that defined in the projections block. At the same time the property names are missing from the list and that is really disadvantageous to the developer as the result would be passed along and the caller needs to know what value belongs to which property. Is there a way to return a map from the Criteria query with property name as the key to the value?
so, the following code:
def c = Trade.createCriteria()
def remicTrades = c.list {
projections {
property('title', 'title')
property('author.name', 'author')
}
def now = new Date()
between('publishedDate', now-365, now)
}
This returns:
[['book1', 'author1']['book2', 'author2']]
Instead I would like it to return:
[[book:'book1', author:'author1'][book:'book2', author:'author2']]
I know I can arrange this way after getting the result but I earnestly feel that the property alias should have been used by the criteria to return a list of map that mimics the result of the SQL query and not a bland list.
Duplicate: Grails queries with criteria: how to get back a map with column?
And the corresponding answer (and solution): https://stackoverflow.com/a/16409512/1263227
Use resultTransformer.
import org.hibernate.criterion.CriteriaSpecification
Trade.withCriteria {
resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)
projections {
property('title', 'title')
property('author.name', 'author')
}
def now = new Date()
between('publishedDate', now-365, now)
}
Agree with your question reasoning, this really should be part of the core GORM solution. That said, here's my workaround;
def props = ['name','phone']
def query = Person.where {}.projections {
props.each{
property(it)
}
}
def people = query.list().collect{ row->
def cols = [:]
row.eachWithIndex{colVal, ind->
cols[props[ind]] = colVal
}
cols
}
println people // shows [['name':'John','phone':'5551212'],['name':'Magdalena','phone':'5552423']]

Django-Admin override default formfield for a foreign keys field

I've read the documentation and tried to implement an override of the default formfield to display only items that belong to the current user (Publisher) in my admin. I have a SimpleSubscriber model with an object named sub_type that's a ForeignKey to the model Product. There is also a Publisher model, and both SimpleSubscriber and Product have ForeignKey objects called publisher. In my admin.py I have this:
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "sub_type":
kwargs["queryset"] = SimpleSubscriber.objects.filter(sub_type=request.user)
return super(SimpleSubscriberAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
In the documentation, it originally had
kwargs["queryset"] = SimpleSubscriber.objects.filter(owner=request.user)
But I got "FieldError: Cannot resolve keyword 'owner' into field" so I replaced owner with sub_type, but that populated the list with subscribers. It should be a list of sub_types (Products).
How do I get this list to show only the sub_types (Products) that belong to the current user (Publisher)?
So I am answering my own question here.
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "sub_type":
subtype = Product.objects.all()
if not request.user.is_superuser:
kwargs["queryset"] = subtype.filter(publisher=request.user)
else:
kwargs["queryset"] = subtype
return super(SimpleSubscriberAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
I needed to change
kwargs["queryset"] = SimpleSubscriber.objects.filter(sub_type=request.user)
to this:
subtype = Product.objects.all()
if not request.user.is_superuser:
kwargs["queryset"] = subtype.filter(publisher=request.user)
else:
kwargs["queryset"] = subtype
I'm learning the hard way about filtering ForeignKey objects. Hope this saves someone a headache.

help with oauthService and linkedin

I am trying to iterate over a list of parameters, in a grails controller. when I have a list, longer than one element, like this:
[D4L2DYJlSw, 8OXQWKDDvX]
the following code works fine:
def recipientId = params.email
recipientId.each { test->
System.print(test + "\n")
}
The output being:
A4L2DYJlSw
8OXQWKDDvX
But, if the list only has one item, the output is not the only item, but each letter in the list. for example, if my params list is :
A4L2DYJlSwD
using the same code as above, the output becomes:
A
4
L
2
D
Y
J
l
S
w
can anyone tell me what's going on and what I am doing wrong?
thanks
jason
I run at the same problem a while ago! My solution for that it was
def gameId = params.gameId
def selectedGameList = gameId.class.isArray() ? Game.getAll(gameId as List) : Game.get(gameId);
because in my case I was getting 1 or more game Ids as parameters!
What you can do is the same:
def recipientId = params.email
if(recipientId.class.isArray()){
// smtg
}else{
// smtg
}
Because what is happening here is, as soon as you call '.each' groovy transform that object in a list! and 'String AS LIST' in groovy means char_array of that string!
My guess would be (from what I've seen with groovy elsewhere) is that it is trying to figure out what the type for recipientId should be since you haven't given it one (and it's thus dynamic).
In your first example, groovy decided what got passed to the .each{} closure was a List<String>. The second example, as there is only one String, groovy decides the type should be String and .each{} knows how to iterate over a String too - it just converts it to a char[].
You could simply make recipientId a List<String> I think in this case.
You can also try like this:
def recipientId = params.email instanceof List ? params.email : [params.email]
recipientId.each { test-> System.print(test + "\n") }
It will handle both the cases ..
Grails provides a built-in way to guarantee that a specific parameter is a list, even when only one was submitted. This is actually the preferred way to get a list of items when the number of items may be 0, 1, or more:
def recipientId = params.list("email")
recipientId.each { test->
System.print(test + "\n")
}
The params object will wrap a single item as a list, or return the list if there is more than one.

Resources