Inside an annotation I do some calculations, and I want the output to be a decimal, with max 8 digits and max 2 decimal. I don't know why but Django ignores decimal_places and max_digits.
Here is my code:
Order.objects.all().annotate(
amount=Coalesce(
Sum(
Case(
When(
Q(payments__status='complete'),
then=F('payments__amount') - (
F('payments__amount') * F('payments__vat')/100
)
), output_field=DecimalField(decimal_places=2, max_digits=8)
)
), 0)
).values('amount')
output = 12.5999999999999996447286321199499070644378662109375
I'm using Django 1.9.5
I had had the same issue in my job, to solve this I've created the following custom aggregate:
class SumDecimal(Func):
function = 'SUM'
name = 'sum'
contains_aggregate = True
output_field = DecimalField()
template = '%(function)s(%(expressions)s)'
def __init__(self, *args, **kwargs):
self.decimal_places = kwargs.pop('decimal_places', None)
super(SumDecimal, self).__init__(*args, **kwargs)
def as_sql(self, compiler, connection, function=None, template=None):
sql = super(SumDecimal, self).as_sql(
compiler=compiler,
connection=connection,
function=function,
template=template)
if self.decimal_places:
sql, params = sql
sql = 'CAST(%s AS DECIMAL(16, %d))' % (sql, self.decimal_places)
return sql, params
return sql
To use this is pretty simple, just use like:
mymodel.objects.all().annotate(sum_value=SumDecimal('value', decimal_places=2))
Related
I'm trying to show manytomany data in json format(without using serializer), here is my models.py
class CustomerInvoice(models.Model):
customer = models.CharField(max_length=50)
items_model = models.ManyToManyField(Item,through='InvoiceItem')
created_at = models.DateTimeField(auto_now_add=True)
class InvoiceItem(models.Model):
item = models.ForeignKey(Item,on_delete=models.CASCADE)
invoice = models.ForeignKey(CustomerInvoice,on_delete=models.CASCADE,related_name='invoice')
quantity = models.IntegerField()
price = models.DecimalField(max_digits=20,decimal_places=2)
is it possible to make a look up base on many to many data?
something like this : Q(items_model__icontains=query_search) ,and also how to return the M2M data into a json format using values() and json.dumps please? this returns the ID Values('items_model') and this dont work Values('items_model__all')
and here is my views.py
def invoices_all_lists(request):
if request.is_ajax():
query_search = request.GET.get('filter')
if query_search:
all_item_qs = CustomerInvoice.objects.all()
a = []
for i in all_item_qs.items_model.all():
a.append(i.item.name)
invoices = CustomerInvoice.objects.annotate(
total=Sum((F('invoice__quantity') * F('invoice__price')),output_field=DecimalField(decimal_places=2,max_digits=20))
).filter(
Q(id__icontains=query_search) | Q(seller__username__icontains=query_search) |
Q(customer__icontains=query_search)).values(
'id','seller__username','customer','total','created_at','items_model').order_by('-id')
else:
all_item_qs = CustomerInvoice.objects.all()
a = []
for data in all_item_qs:
for i in data.items_model.all():
a.append(i.item.name)
invoices = CustomerInvoice.objects.annotate(
total=Sum((F('invoice__quantity') * F('invoice__price')) ,output_field=DecimalField(decimal_places=2,max_digits=20))
).values(
'id','seller__username','customer','total','created_at','items_model').order_by('-id')
start_from = 0
if request.GET.get('start'):
start_from = int(request.GET.get('start'))
limit = 10
if request.GET.get('limit'):
limit = int(request.GET.get('limit'))
data_lists = []
for index,value in enumerate(invoices[start_from:start_from+limit],start_from):
value['counter'] = index+1
data_lists.append(value)
data = {
'objects':data_lists,
'length':invoices.count(),
}
return HttpResponse(json.dumps(data, indent=4, sort_keys=True, default=str),'application/json')
else:
return redirect('invoiceapp:list-all-invoice')
can i add this part of the code into the query please?
a = []
for data in all_item_qs:
for i in data.items_model.all():
a.append(i.item.name)
note : i've used datatable server side in the client side
Unfortunately I've no Idea what's the issue coding in this way. I try to run a select statement within a class - the result is: TypeError: open_select() missing 1 required positional argument: 'query'.
Does anybody have an idea? Thx in advance
class finDB_exec:
def __init__(self):
self.dbcn = db.connect(host='localhost',
user='root',
password='xxx',
database='xxxx')
self.dbcr = self.dbcn.cursor()
def db_commit(self):
self.dbcn.commit()
def db_close(self):
self.dbcr.close()
self.dbcn.close()
##############################################################################
#### 1) Open Select
##############################################################################
def open_select(self,query):
try:
self.cr = self.dbcr()
self.cr.execute(query)
self.db_commit()
result = self.cursor.fetchall()
return result
except:
pass
query = 'Select * from tbl_companies limit 10'
res = finDB_exec.open_select(query)
I've got a query that normally looks like
def get_models_with_children(ids):
query = MyModel.query.filter(MyModel.id.in_(ids))
.join(Child, Child.parent_id = Child.id)
.groupBy(MyModel.id)
.having(func.count(Child.id) > 0)
return query.all()
Sometimes, I want to actually retrieve the count, as well. I can make that happen easily enough:
def get_models_with_children(ids, return_count):
query = MyModel.query
if return_count:
query = query.add_columns(func.count(Child.id).label("child_count"))
query = query.filter(MyModel.id.in_(ids))
.join(Child, Child.parent_id = Child.id)
.groupBy(MyModel.id)
.having(func.count(Child.id) > 0)
return query.all()
This works fine, but now, instead of a List[MyModel] coming back, I've got a differently shaped result with MyModel and child_count keys. If I want the MyModel's id, I do result[0].id if I didn't add the count, and result[0].MyModel.id if I did.
Is there any way I can flatten the result, so that the thing that's returned looks like a MyModel with an extra child_count column?
def do_stuff_with_models():
result = get_models_with_children([1, 2, 3], True)
for r in result:
# can't do this, but I want to:
print(r.id)
print(r.child_count)
# instead I have to do this:
print(r.MyModel.id)
print(r.child_count)
sqlalchemy.util.KeyedTuple is the type * of differently shaped result with MyModel and child_count keys:
Result rows returned by Query that contain multiple
ORM entities and/or column expressions make use of this
class to return rows.
You can effectively flatten them by explictly specifying the columns for your query. Here follows a complete example (tested on SQLAlchemy==1.3.12).
Plain table column attribute
Models:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
user_id = sa.Column(sa.Integer, sa.Sequence('user_id_seq'), primary_key=True)
username = sa.Column(sa.String(80), unique=True, nullable=False)
def __repr__(self):
return f'User({self.user_id!r}, {self.username!r})'
class Token(Base):
__tablename__ = 'token'
token_id = sa.Column(sa.Integer, sa.Sequence('token_id_seq'), primary_key=True)
user_id = sa.Column(sa.Integer, sa.ForeignKey('user.user_id'), nullable=False)
user = sa.orm.relationship('User')
value = sa.Column(sa.String(120), nullable=False)
def __repr__(self):
return f'Token({self.user.username!r}, {self.value!r})'
Connect and fill some data:
engine = sa.create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sa.orm.sessionmaker(bind=engine)
session = Session()
user1 = User(username='joe')
user2 = User(username='john')
token1 = Token(user=user1, value='q1w2e3r4t56')
session.add_all([user1, user2, token1])
session.commit()
Now, let's define the "virtual" column as whether user has a token:
query = session.query(User)
exists = (
sa.exists()
.where(User.user_id == Token.user_id)
.correlate(User)
.label("has_token")
)
query = query.add_columns(exists)
query.all() # [(User(1, 'joe'), True), (User(2, 'john'), False)]
It's the undesired shape. And here's how to flatten it:
query = session.query(*[getattr(User, n) for n in User.__table__.columns.keys()])
query = query.add_columns(exists)
query.all() # [(1, 'joe', True), (2, 'john', False)]
It's all possible to define columns for an existing query, given that you know the model:
query = session.query(User)
# later down the line
query = query.with_entities(*[
getattr(User, n) for n in User.__table__.columns.keys()])
query = query.add_columns(exists)
query.all() # [(1, 'joe', True), (2, 'john', False)]
Column bundle
The same can be achieved with sqlalchemy.orm.Bundle and passing single_entity to it.
bundle = sa.orm.Bundle(
'UserBundle', User.user_id, User.username, exists, single_entity=True)
query = session.query(bundle)
query.all() # [(1, 'joe', True), (2, 'john', False)]
Issue with relationship attribute
With complex models it gets complicated. It's possible to inspect the model (mapped class) attributes with sqlalchemy.orm.mapper.Mapper.attrs and take class_attribute:
# replace
[getattr(User, n) for n in User.__table__.columns.keys()]
# with
[mp.class_attribute for mp in sa.inspect(User).attrs]
But in this case relationship attributes turn into their target tables in FROM clause of the query without ON clause, effectively producing a cartesian product. And the "joins" have to be defined manually, so it's not a good solution. See this answer and a SQLAlchemy user group discussion.
Query expression attribute
Myself I ended up using query expressions, because of the issues with relationships in existing code. It's possible to get away with minimal modification of the model, with query-time SQL expressions as mapped attributes.
User.has_tokens = sa.orm.query_expression()
...
query = query.options(sa.orm.with_expression(User.has_tokens, exists))
query.all() # [User(1, 'joe'), User(2, 'john')]
[u.has_tokens for u in query.all()] # [True, False]
* Actually it's generated on-the-fly sqlalchemy.util._collections.result with MRO of sqlalchemy.util._collections.result, sqlalchemy.util._collections._LW, class sqlalchemy.util._collections.AbstractKeyedTuple, tuple, object, but that's details. More details on how the class is created with lightweight_named_tuple are available in this answer.
I want to make a query with [ORDER BY CONVERT (name USING gbk) ASC] ,so that foreignkey list in admin change/add form can be sorted by Chinese Pinyin , is it possible?
DB: mysql ,
Sample class are blow (Character sets of table commoninfo : utf8_general_ci):
class CommonInfo(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Personal(CommonInfo):
first_name = models.CharField(max_length=128,null=True, blank=True, default = None)
last_name = models.CharField(max_length=128,null=True, blank=True, default = None)
class Profile(models.Model):
personal_info = models.ForeignKey(Personal, null=True, blank=True, default = None)
mobile = models.CharField(max_length=32,default='',null=True, blank=True)
I tried below 2 methods in ProfileAdmin but neither works
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "personal_info":
#raw_sql = 'SELECT id as commoninfo_ptr_id, name FROM share_commoninfo ORDER BY CONVERT (name USING gbk) ASC'
#raw_querySet = Personal.objects.raw(raw_sql)
#kwargs["queryset"] = raw_querySet
querySet = Personal.objects.extra(order_by=['CONVERT (name USING gbk)'])
kwargs["queryset"] = querySet
return super(ProfileAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
admin.site.register(models.Profile, ProfileAdmin)
FieldError: Cannot resolve keyword 'CONVERT (name USING gbk)' into
field. Choices are: commoninfo_ptr, commoninfo_ptr_id, first_name, id,
last_name, name, profile, Check fields/fieldsets/exclude attributes of
class ProfileAdmin.
from django.db.models import Func, Value
class Convert(Func):
def __init__(self, expression, transcoding_name, **extra):
super(Convert, self).__init__(
expression, transcoding_name=Value(transcoding_name), **extra)
def as_mysql(self, compiler, connection):
self.function = 'CONVERT'
self.template = '%(function)s(%(expressions)s AS %(transcoding_name)s)'
return super(Conver, self).as_sql(compiler, connection)
Usage:
queryset.order_by(Convert('name', 'gbk').asc())
Documentation:
Func() expressions
Examples of database functions
you can use extra
queryset.extra(
select={'convert_name': 'convert(name using gbk)'},
order_by=['convert_name']
)
I'm struggling with the formatting on a mysql query and I was hoping you could point me in the right direction. Here are the queries
sql = "SELECT price FROM inventory WHERE card_name = %s AND card_set = %s"
sql_rare = "SELECT rarity FROM inventory WHERE card_name = %s AND card_set = %s"
sql_count = "SELECT count(*) FROM inventory WHERE card_name = %s AND card_set = %s
When I run the following code, utilizing the sql_count query, i get an error saying:
File "C:\Users\Spencer\Desktop\Python Programs\PythonMTG\Revision3AutoAndManual\51515\magicassistantcsv.py", line 264, in output_card
for row in getmtgprice.query(sql_count, ([card_name, set_name])):
TypeError: query() takes exactly 4 arguments (3 given)
Here is the code producing this error:
getmtgprice = PriceCheck()
for row in getmtgprice.query(sql_count, ([card_name, set_name])):
if row[0] ==0:
priced_card = '0.00'
And here is the PriceCheck function:
class PriceCheck(object):
def __init__(self):
self.conn = MySQLdb.connect(host='localhost', user='root', passwd='', db='mscan')
self.c = self.conn.cursor()
def query(self, arg, cardname, setname):
self.c.execute(arg, cardname, setname)
return self.c
def __del__(self):
self.conn.close()
Do you see where I went wrong?
Your query method takes separate arguments for cardname and setname, not a list containing both. So, instead of:
for row in getmtgprice.query(sql_count, ([card_name, set_name])):
You should have:
for row in getmtgprice.query(sql_count, card_name, set_name):