Django Admin Column Sort Descending - django

When using Django admin with grappelli I would like that a click on a column header will sort the table by descending order.
(I don't want a default ordering to the columns by defining the ordering field in the Model Meta Class.)
The default behavior is Ascending.
The first click should order like this:

Rewrite the def result_headers(cl)
add these 2 lines:
.....
.....
th_classes = ['sortable']
order_type = ''
#new lines
default_order_type = getattr(attr, "admin_order_first_type", None)
new_order_type = default_order_type if default_order_type else 'asc'
#end of new lines
sort_priority = 0
sorted = False
...
...
now in the ModelAdmin you can:
list_display = ('number_of_players', ....)
def number_of_players(self, team):
return intcomma(team.number_of_players)
number_of_players.short_description = '# num of players'
number_of_players.admin_order_field = 'number_of_players'
number_of_players.admin_order_first_type = 'desc' #will make the column to be ordered desc first
I tested it and it works

Related

how to conditionally add filter fields and value in Django

Consider the below sample model.
class Server(models.Model):
os = models.CharField(max_length=30)
server_owner = models.CharField(max_length=30)
server_located = models.CharField(max_length=20)
server_name = models.CharField(max_length=20)
...
...
In the future, this model will have more fields.
So from the request, I would get list of values that I need to filter. Consider the below sample data.
os_list = ["Windows", "Linux", "Mac"]
server_owner_list = ["John", "Will", "Jake", "Shyam"],
server_located = ["India", "USA"]
server_name = []
...
...
...
I want to exclude the lists which are empty. From the above example, server_name is an empty list. So while filtering from the Server model I want to add os_list, server_owner_list and server_located and exclude the server_name. In the future, more fields will be added to the DB model, and I might receive an extra list of values from the client request. which could have empty list of array. So I don't want to write a bunch of if-else conditions to check which lists are empty and not empty.
Can anyone please help me how can I write scalable ORM for this.
Edit:
so from a frontend react table, I will use column filtering. from the column dropdown, if the user selects the data from the column dropdown, selected data will be there in the array format. If the user resets the data which he had selected previously will be an empty array. so when the user presses the filter the data will be passed to the backend.
So in the backend, I would receive the empty list of array. So if
os_filter_val = request.query_params.getlist('osFilter[]')
server_owner_list = request.query_params.getlist('serverOwnerFilter[]')
Server.Objects.filter(
os__in=os_filter_val,
server_owner__in=server_owner_list)
So in the above example consider for server_owner_list I got an empty array and I don't want to include it in the filter queryset what I would generally do is.
if len(os_filter_val) > 0 and len(server_owner_list) > 0:
Server.Objects.filter(
os__in=os_filter_val,
server_owner__in=server_owner_list)
else if(os_filter_val) > 0 and len(server_ownder_list) == 0:
Server.Objects.filter(os__in=os_filter_val)
....
....
....
So here if fields are more then I have to write a lot of if-else condition to check whether there are any empty lists. If its there do not include it in the queryset.
So is there any way where we can check for the empty list and add only the nonempty lists without writing the if-else conditions.
Try this code.Change OR and AND as per your requirement and also add if condition in your url parameters.
from django.db.models import Q
os_filter_val = request.query_params.getlist('osFilter[]')
server_owner_list = request.query_params.getlist('serverOwnerFilter[]')
q_objects = Q()
if os_filter_val:
q_objects.add(Q(os__in=os_filter_val), Q.OR)
if server_owner_list:
q_objects.add(Q(server_owner__in=server_owner_list), Q.OR)
Server.Objects.filter(q_objects,)
For filtering, in general, I recommend you Django-Filters. It will save you a lot of if-else conditions. For specific in filter, you can write a custom filter class:
from rest_framework import generics
from django_filters import rest_framework as filters
from myapp import Server
class ServerFilter(filters.FilterSet):
osFilter = filters.NumberFilter(field_name="id", lookup_expr='in')
class Meta:
model = Server
fields = ['osFilter',]
class ServerList(generics.ListAPIView):
queryset = Server.objects.all()
serializer_class = ServerSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = ServerFilter
But besides that I recommend you to read about some tips with in null and ranges and base filtering

Remove duplicates from QuerySelectField

i'm running into issues with the following, and I'm wondering if it is even possible.
I have a flask-admin adminview setup, with an extra form field which shows a dropdown based on a specific column (category) in the sql model. See code for clarification:
model:
class Item(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(128), index = True)
category = db.Column(db.String(16))
I have the extra form field in Flask-Admin as follows:
form_extra_fields = {
'category': QuerySelectField(
label='Categories',
query_factory = lambda: db.session.query(Item),
get_label = 'category',
)
}
This all works fine except if there are duplicates in the category column, then the dropdown is populated with these duplicate values. Is it possible to remove those duplicates from dropdown, or at least only show the unique values?
Basically I solved this by overriding a class method in QuerySelectField class as follows, by appending unique labels to a list and check if every next label is in that list. I'm still thinking there should be a better way though...
def iter_choices(self):
labels = []
if self.allow_blank:
yield ('__None', self.blank_text, self.data is None)
for pk, obj in self._get_object_list():
if self.get_label(obj) not in labels:
labels.append(self.get_label(obj))
yield (pk, self.get_label(obj), obj == self.data)

Django How To Query ManyToMany Relationship Where All Objects Match

I have the following models:
## Tags for issues
class issueTags(models.Model):
name = models.CharField(max_length=400)
class issues(models.Model):
tags = models.ManyToManyField(issueTags,blank = True)
In my view I get an array from some client side JavaScript i.e.
(Pdb) array_data = request.POST['arr']
(Pdb) array_data
'["2","3"]'
How should I filter my issues object to find all issues which match all tags in the array? (the 2,3 are the ID values for tag__id.
If there is a better way to arrange the objects that would also work so I can search in this fashion.
At the time of writing this, the existing answers are either incorrect (e.g. filtering matching all Issues that have any of the specified tags and the correct tag count) or inefficient (e.g. attaching filters in a loop).
For the following models:
class IssueTag(models.Model):
name = models.CharField(max_length=400, blank=True)
class Issue(models.Model):
label = models.CharField(max_length=50, blank=True)
tags = models.ManyToManyField(IssueTag, related_name='issues')
I suggest using Django Annotation in conjunction with a filter like so:
from django.db.models import Count, Q
tags_to_match = ['tag1', 'tag2']
issues_containing_all_tags = Issue.objects \
.annotate(num_correct_tags=Count('tags',
filter=Q(tags__name__in=tags_to_match))) \
.filter(num_correct_tags=2)
to get all Issues that have all required tags (but may have additional tags, as is required in the question).
This will produce the following SQL query, that resolves all tag matching in a single IN clause:
SELECT "my_app_issue"."id", "my_app_issue"."label",
COUNT("my_app_issue_tags"."issuetag_id")
FILTER (WHERE "my_app_issuetag"."name" IN ('tag1', 'tag2'))
AS "num_correct_tags"
FROM "my_app_issue"
LEFT OUTER JOIN "my_app_issue_tags" ON ("my_app_issue"."id" = "my_app_issue_tags"."issue_id")
LEFT OUTER JOIN "my_app_issuetag" ON ("my_app_issue_tags"."issuetag_id" = "my_app_issuetag"."id")
GROUP BY "my_app_issue"."id", "my_app_issue"."label"
HAVING COUNT("my_app_issue_tags"."issuetag_id")
FILTER (WHERE ("my_app_issuetag"."name" IN ('tag1', 'tag2'))) = 2;
args=('tag1', 'tag2', 'tag1', 'tag2', 2)
I haven't tested this, but I think you could do the following:
from django.db.models import Q
array_data = array_data.split(',')
issues.objects.filter(
tags__in=array_data,
).exclude(
# Exclude any that aren't in array_data
~Q(tags__in=array_data)
).annotate(
matches=Count(tags, distinct=True)
).filter(
# Make sure the number found is right.
matches=len(array_data)
)
FYI, you should be using Issue, IssueTag for your model names to follow Django's naming pattern.
It isn't most elegant solution or pythonic but I ended up just looping around the resulting filter.
def filter_on_category(issue_object,array_of_tags):
#keep filtering to make an and
i = 0
current_filter = issue_object
while (i < (len(array_of_tags))):
#lets filter again
current_filter=current_filter.filter(tags__id__in=array_of_tags[i])
i=i+1
return current_filter
Django field lookups argument (__) for many-to-many fields needs list argument. I have created a dummy list for each array element of IssueTags and pass it to lookups argument and it works as expected.
Let you have this models:
class IssueTags(models.Model):
name = models.CharField(max_length=400)
class Issues(models.Model):
tags = models.ManyToManyField(IssueTags,blank = True)
You want to get Issues which contains all of these IssueTags = ["1","2","3"]
issue_tags_array = ["1","2","3"]
#First initialize queryset
queryset = Issues.objects.all()
i = 0
while i < len(issue_tags_array):
#dummy issue_tag list
issue_tag = [issue_tags_array[i]]
#lets filter again
queryset = queryset.filter(tags__id__in=issue_tag)
i=i+1
return queryset

Django ORM. Joining subquery

I have a table which contains list of some web sites and a table with statistics of them.
class Site(models.Model):
domain_name = models.CharField(
max_length=256,
unique=True,
)
class Stats(models.Model):
date = models.DateField()
site = models.ForeignKey('Site')
google_pr = models.PositiveIntegerField()
class Meta:
unique_together = ('site', 'date')
I want to see all sites and statistics for a concrete date. If a stats record for the date doesn't exist, then the selection must contain only site.
If I use:
Site.objects.filter(stats__date=my_date)
I will not get sites which have no records for my_date in stats table. Because in this case the SQL query will be like the following:
SELECT *
FROM site
LEFT OUTER JOIN stats ON site.id = stats.site_id
WHERE stats.date = 'my_date'
The query condition will exclude records with NULL-dates and sites without stats will be not included to the selection.
In my case I need join stats table, which has already been filtered by date:
SELECT *
FROM site
LEFT OUTER JOIN
(SELECT *
FROM stats
WHERE stats.date = 'my-date') AS stats
ON site.id = stats.site_id
How can I translate this query to Django ORM?
Thanks.
In Django v2.0 use FilteredRelation
Site.objects.annotate(
t=FilteredRelation(
'stats', condition=Q(stats__date='my-date')
).filter(t__google_pr__in=[...])
I had a similar problem and wrote the following utility function for adding left outer join on a subqueryset using Django ORM.
The util is derived from a solution given to add custom left outer join to another table (not subquery) using Django ORM. Here is that solution: https://stackoverflow.com/a/37688104/2367394
Following is the util and all related code:
from django.db.models.fields.related import ForeignObject
from django.db.models.options import Options
from django.db.models.sql.where import ExtraWhere
from django.db.models.sql.datastructures import Join
class CustomJoin(Join):
def __init__(self, subquery, subquery_params, parent_alias, table_alias, join_type, join_field, nullable):
self.subquery_params = subquery_params
super(CustomJoin, self).__init__(subquery, parent_alias, table_alias, join_type, join_field, nullable)
def as_sql(self, compiler, connection):
"""
Generates the full
LEFT OUTER JOIN (somequery) alias ON alias.somecol = othertable.othercol, params
clause for this join.
"""
params = []
sql = []
alias_str = '' if self.table_alias == self.table_name else (' %s' % self.table_alias)
params.extend(self.subquery_params)
qn = compiler.quote_name_unless_alias
qn2 = connection.ops.quote_name
sql.append('%s (%s)%s ON (' % (self.join_type, self.table_name, alias_str))
for index, (lhs_col, rhs_col) in enumerate(self.join_cols):
if index != 0:
sql.append(' AND ')
sql.append('%s.%s = %s.%s' % (
qn(self.parent_alias),
qn2(lhs_col),
qn(self.table_alias),
qn2(rhs_col),
))
extra_cond = self.join_field.get_extra_restriction(
compiler.query.where_class, self.table_alias, self.parent_alias)
if extra_cond:
extra_sql, extra_params = compiler.compile(extra_cond)
extra_sql = 'AND (%s)' % extra_sql
params.extend(extra_params)
sql.append('%s' % extra_sql)
sql.append(')')
return ' '.join(sql), params
def join_to(table, subquery, table_field, subquery_field, queryset, alias):
"""
Add a join on `subquery` to `queryset` (having table `table`).
"""
# here you can set complex clause for join
def extra_join_cond(where_class, alias, related_alias):
if (alias, related_alias) == ('[sys].[columns]',
'[sys].[database_permissions]'):
where = '[sys].[columns].[column_id] = ' \
'[sys].[database_permissions].[minor_id]'
children = [ExtraWhere([where], ())]
return where_class(children)
return None
foreign_object = ForeignObject(to=subquery, from_fields=[None], to_fields=[None], rel=None)
foreign_object.opts = Options(table._meta)
foreign_object.opts.model = table
foreign_object.get_joining_columns = lambda: ((table_field, subquery_field),)
foreign_object.get_extra_restriction = extra_join_cond
subquery_sql, subquery_params = subquery.query.sql_with_params()
join = CustomJoin(
subquery_sql, subquery_params, table._meta.db_table,
alias, "LEFT JOIN", foreign_object, True)
queryset.query.join(join)
# hook for set alias
join.table_alias = alias
queryset.query.external_aliases.add(alias)
return queryset
join_to is the utility function you want to use. For your query you can use it in as follows:
sq = Stats.objects.filter(date=my_date)
q = Site.objects.filter()
q = join_to(Site, sq, 'id', 'site_id', q, 'stats')
And following statement would print a query similar to you example query (with subquery).
print q.query
Look at it this way: you want to see statistics with accompanying site data for certain date, which translates to:
Stats.objects.filter(date=my_date).select_related('site')

update form - using formsets - initial values unknown until runtime

I'm new to python and Django and have a simple question on how to update f form that has multiple fields of same type: I have been trying to do this with formsets:
I have a simple model to store categories:
class Category(BaseModel):
categoryText = db.StringProperty()
parentCat = db.IntegerProperty()
I want to create a form that would display all available categories in input fields so they could all be edited:
using formsets to display mulitple rows of the same type:
EDIT:
figured it out:
I had to create a list of dictionary items
categories = Category.objects.all()
initialStuff = []
oneFormV={}
for cat in categories:
oneFormV.clear()
oneFormV["categoryText"]=cat.categoryText
oneFormV["parentCat"]=str(cat.parentCat)
oneFormV["catID"]=str(cat.key().id())
initialStuff.append(oneFormV.copy())
def showCategories(request):
if request.POST:
# code to update db
else:
categories = Category.objects.all()
initialStuff = []
for cat in categories:
initialStuff += "'categoryText':u'" + cat.categoryText +"'," + "'parentCat':u'" + str(cat.parentCat) +"'," + "'catID':u'" + str(cat.key().id()) + "'"
initialStuff = initialStuff [:-1] # remove last comma
CategoryFormSet = formset_factory(CategoryForm,extra=categories.count())
formset = CategoryFormSet(initial= initialStuff )
return render_to_response('adminCategories.html', {'formset': formset})
I am having problem with populating the initial data. When I generate in a loop it gives me errors :
class CategoryForm(forms.Form):
categoryText = forms.CharField()
parentCat = forms.CharField()
catID = forms.CharField()
I am assuming I need to store the ID for the fields to update them!
Finally my question:
1) Am I doing this right or is there an easier way to accomplish this?
2) my problem has been passing initial values to a formset with initial values unknown until run time.
3) should I forget about formsets and do this by adding fields to the form with init?
4) what is the correct way of initializing form fields in a formset?
AM
initialStuff must be list of dicts, not list of strings:
for cat in categories:
initialStuff.append( { categoryText: cat.categoryText,
...
}
)
So, don't remove the last comma.
If you use the operation += for list and str you get a list of strings (each str has length 1).
Please view the next: http://docs.djangoproject.com/en/dev/topics/forms/formsets/#using-initial-data-with-a-formset