How to perform this sql in django model? - python

SELECT *, SUM( cardtype.price - cardtype.cost ) AS profit
FROM user
LEFT OUTER JOIN card ON ( user.id = card.buyer_id )
LEFT OUTER JOIN cardtype ON ( card.cardtype_id = cardtype.id )
GROUP BY user.id
ORDER BY profit DESC
I tried this:
User.objects.extra(select=dict(profit='SUM(cardtype.price-cardtype.cost)')).annotate(sum=Sum('card__cardtype__price')).order_by('-profit')
But Django automatically added SUM( cardtype.price ) to the GROUP BY clause, and the SQL doesn't run.
Can this be done without raw SQLs?
Provide the model, never mind these Chinese characters :)
class User(models.Model):
class Meta:
verbose_name = "用户"
verbose_name_plural = "用户"
ordering = ['-regtime']
user_status= (
("normal", "正常"),
("deregistered", "注销"),
("locked", "锁定"),
)
name = models.CharField("姓名", max_length=20, db_index=True)
spec_class = models.ForeignKey(SpecClass, verbose_name="专业班级")
idcard = models.CharField("身份证号", max_length=18)
mobileno = models.CharField("手机号", max_length=11)
password = models.CharField("密码", max_length=50) # plain
address = models.CharField("住址", max_length=100)
comment = models.TextField("备注")
certserial = models.CharField("客户证书序列号", max_length=100)
regtime = models.DateTimeField("注册时间", default=datetime.datetime.now)
lastpaytime = models.DateTimeField("上次付款时间", default=datetime.datetime.now)
credit = models.FloatField("信用额度", default=100)
money = models.FloatField("余额", default=0)
use_password = models.BooleanField("使用密码")
use_fetion = models.BooleanField("接收飞信提示")
status = models.CharField("账户状态", choices = user_status, default="normal", max_length=20, db_index=True)
def __unicode__(self):
return self.name
class CardType(models.Model):
class Meta:
verbose_name = "点卡类型"
verbose_name_plural = "点卡类型"
ordering = ['name']
name = models.CharField("类型名称", max_length=20, db_index=True)
note = models.CharField("说明", max_length=100)
offcial = models.BooleanField("官方卡", default=True)
available = models.BooleanField("可用", default=True, db_index=True)
payurl = models.CharField("充值地址", max_length=200)
price = models.FloatField("价格")
cost = models.FloatField("进货价格")
def __unicode__(self):
return u"%s(%.2f元%s)" % (self.name, self.price, u", 平台卡" if not self.offcial else "")
def profit(self):
return self.price - self.cost
profit.short_description = "利润"
class Card(models.Model):
class Meta:
verbose_name = "点卡"
verbose_name_plural = "点卡"
ordering = ['-createtime']
card_status = (
("instock", "未上架"),
("available", "可用"),
("sold", "已购买"),
("invalid", "作废"),
("returned", "退卡"), # sell to the same person !
("reselled", "退卡重新售出"),
)
cardtype = models.ForeignKey(CardType, verbose_name="点卡类型")
serial = models.CharField("卡号", max_length=40)
password = models.CharField("卡密", max_length=20)
status = models.CharField("状态", choices = card_status, default="instock", max_length=20, db_index=True)
createtime = models.DateTimeField("入库时间")
buytime = models.DateTimeField("购买时间", blank=True, null=True)
buyer = models.ForeignKey(User, blank=True, null=True, verbose_name="买家")
def __unicode__(self):
return u'%s[%s]' % (self.cardtype.name, self.serial)

First, one of the outer joins appears to be a bad idea for this kind of thing. Since you provided no information on your model, I can only guess.
Are you saying that you may not have a CARD for each user? That makes some sense.
Are you also saying that some cards don't have card types? That doesn't often make sense. You haven't provided any details. However, if a Card doesn't have a Card Type, I'll bet you have either problems elsewhere in your application, or you've chosen really poor names that don't provide the least clue as to what these things mean. You should fix the other parts of your application to assure that each card actually does have a card type. Or you should fix your names to be meaningful.
Clearly, the ORM statement uses inner joins and your SQL uses outer joins. What's the real question? How to do outer joins correctly?
If you take the time to search for [Django] and Left Outer Join, you'll see that the Raw SQL is a terrible idea.
Or is the real question how to do the sum correctly? From your own answer it appears that the SQL is wrong and you're really having trouble with the sum. If so, please clean up the SQL to be correct.
If the outer joins are part of the problem -- not just visual noise -- then you have to do something like this for an outer join with a sum.
def user_profit():
for u in User.objects.all():
profit = sum[ t.price - t.cost
for c in u.card_set.all()
for t in c.cardtype_set.all() ]
yield user, profit
In your view function, you can then provide the value of function to the template to render the report. Since it's a generator, no huge list is created in memory. If you need to paginate, you can provide the generator to the paginator and everything works out reasonably well.
This is often of comparable speed to a complex raw SQL query with a lot of outer joins.
If, indeed, the card to card-type relationship is not actually optional, then you can shorten this, somewhat. You still have an outer join to think about.
def user_profit():
for u in User.objects.all():
profit = sum[ c.cardtype.price - c.cardtype.cost
for c in u.card_set.all() ]
yield user, profit

Well, I found this
Sum computed column in Django QuerySet
Have to use raw SQL now...
Thank you two!

Related

Django use LEFT JOIN instead of INNER JOIN

I have two models: Comments and CommentFlags
class Comments(models.Model):
content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s",
on_delete=models.CASCADE)
object_pk = models.CharField(_('object ID'), db_index=True, max_length=64)
content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")
submit_date = models.DateTimeField(_('date/time submitted'), default=None, db_index=True)
...
...
class CommentFlags(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="comment_flags",
on_delete=models.CASCADE)
comment = models.ForeignKey(Comment, related_name="flags", on_delete=models.CASCADE)
flag = models.CharField(max_length=30, db_index=True)
...
...
CommentFlags flag can have values: like, dislike etc.
Problem Statement: I want to get all Comments sorted by number of likes in DESC manner.
Raw Query for above problem statement:
SELECT
cmnts.*, coalesce(cmnt_flgs.num_like, 0) as num_like
FROM
comments cmnts
LEFT JOIN
(
SELECT
comment_id, Count(comment_id) AS num_like
FROM
comment_flags
WHERE
flag='like'
GROUP BY comment_id
) cmnt_flgs
ON
cmnt_flgs.comment_id = cmnts.id
ORDER BY
num_like DESC
I have not been able to convert the above query in Django ORM Queryset.
What I have tried so far...
>>> qs = (Comment.objects.filter(flags__flag='like').values('flags__comment_id')
.annotate(num_likes=Count('flags__comment_id')))
which generates different query.
>>> print(qs.query)
>>> SELECT "comment_flags"."comment_id",
COUNT("comment_flags"."comment_id") AS "num_likes"
FROM "comments"
INNER JOIN "comment_flags"
ON ("comments"."id" = "comment_flags"."comment_id")
WHERE "comment_flags"."flag" = 'like'
GROUP BY "comment_flags"."comment_id",
"comments"."submit_date"
ORDER BY "comments"."submit_date" ASC
LIMIT 21
Problem with above ORM queryset is, it uses InnerJoin and also I don't know how it adds submit_date in groupby clause.
Can you please suggest me a way to convert above mentioned Raw query to Django ORM queryset ?
You can try using filter argument in Count:
qs = (Comment.objects.all()
.annotate(num_likes=Count('flags__comment_id', filter=Q(flags__flag='like'))))
It may produce slightly different query that you're expecting, depending on the database backend, but it should have equivalent behavior.

How to iterate through all foreign keys pointed at an object in Django without using _set notation?

I am fairly new to Django, but I am working on an application that will follow a CPQ flow or Configure, Price, Quote. The user should select the product they would like to configure as well as the options to go with it. Once selected, the program should query an external pricing database to calculate price. From there the program should output the pricing & text data onto a PDF quote. I was able to get the application working using the specific product inheriting from a base product class. The issue is now that I've created a second product child class, I cannot use a singular "related_name". I've omitted the lists associated with the drop down fields to help with readability, but I've posted my models.py file below.
Is there a way I can iterate through Product objects that are pointing to a Quote object with a foreign key? A lot of answers I've found on SO relating to this were able to be solved either by specifying the "_set" or "related_name". I've seen other answers use the select_related() method, however, I can't seem to get the query right as the program won't know which set it needs to look at. A quote could have any mix of product instances tied to it, so am unsure how to handle that query. Again, I have been using django under 6 months, so I am a bit green. I am not sure if I am just not fundamentally understanding the big picture here. I thought about instead of using inheritance, to make Product a standalone class and to save the Compact or WRC info to it so I could just use one "related_name", but also thought that would just create another nested layer that would still fail.
Any help would be very appreciated! I've definitely hit the wall.
models.py
class Quote(models.Model):
project_name = models.CharField(max_length=256,blank=True)
customer_first_name = models.CharField(max_length=256,blank=True)
customer_last_name = models.CharField(max_length=256,blank=True)
company_name = models.CharField(max_length=256, blank=True)
address1 = models.CharField(max_length=256, blank=True, help_text ="Address")
address2 = models.CharField(max_length=256, blank=True)
city = models.CharField(max_length=256, blank=True, default="")
state = models.CharField(max_length=256, blank=True, default="")
zip_code = models.CharField(max_length=256, blank=True, default="")
country = models.CharField(max_length=256, blank=True, default="")
phone = PhoneField(blank=True)
email = models.EmailField(max_length=254,blank=True)
grand_total = models.FloatField(default=0)
create_date = models.DateTimeField(default = timezone.now)
class Product(models.Model):
class Meta:
abstract = True
price = models.FloatField(default=0)
total_price = models.FloatField(default=0)
quantity = models.IntegerField()
quote = models.ForeignKey('quote.Quote', on_delete=models.CASCADE)
quantity = models.IntegerField()
class Compact(Product):
base_size = models.CharField(choices=size, max_length = 256)
filter = models.CharField(choices=filter_list, max_length = 256)
product_name = models.CharField(max_length=256,default="Compact")
class WRC(Product):
base_size = models.CharField(choices=size, max_length = 256)
construction = models.CharField(choices=construction_list, max_length = 256)
outlet = models.CharField(choices=outlet_list, max_length = 256)
product_name = models.CharField(max_length=256,default="WRC")
I was able to figure out my issue, but wanted to answer in case someone came across a similar problem as myself. I was able to get get all product objects attached to a quote instance dynamically by modifying the get_context_data() method of my QuoteDetailView. I also needed to use the django library NestedObjects from django.contrib.admin.utils to grab all related objects to the quote instance. I also added a timestamp field to the Product class to be able to sort them. QuoteDetailView copied below.
class QuoteDetailView(FormMixin,DetailView):
model = Quote
form_class = ProductSelectForm
def get_context_data(self, **kwargs):
### collects related objects from quote
collector = NestedObjects(using=DEFAULT_DB_ALIAS)
collector.collect([kwargs['object']])
### slice off first element which is the quote itself
related_objects = collector.nested()
related_objects = related_objects[1:]
### get context data for qoute object
context = super().get_context_data(**kwargs)
context['now'] = timezone.now()
### if number of list items is above 0, then add them to the context
### and sort by timestamp
if len(related_objects) != 0:
context['items'] = sorted(related_objects[0], key=lambda x: x.timestamp)
return context

Calculate sum of items in a date_range queryset if matched by foreign_key_id and skip the rest

I have a 100k entries per day and I am using them to output in an API(i have a limit and and offset by default). I want to calculate values in my queryset if they have a common owner_id and leave the rest as it is if no common owner for the date delta
What i am doing now but doesnt look to be correct( it doest calculate some data correct tho, but some data is increased as well for some reason, which should have not been)
TrendData.objects.filter(owner__trend_type__mnemonic='posts').filter(
date_trend__date__range=[date_from, date_to]).values('owner__name').annotate(
views=(Sum('views') / date_delta),
views_u=(Sum('views_u') / date_delta),
likes=(Sum('likes') / date_delta),
shares=(Sum('shares') / date_delta),
interaction_rate=(
Sum('interaction_rate') / date_delta),
)
date_delta = date_to - date_from #<- integer
my models are:
class Owner(models.Model):
class Meta:
verbose_name_plural = 'objects'
TREND_OWNERS = Choices('group', 'user')
link = models.CharField(max_length=255)
name = models.CharField(max_length=255)
owner_type = models.CharField(choices=TREND_OWNERS, max_length=50)
trend_type = models.ForeignKey(TrendType, on_delete=models.CASCADE)
def __str__(self):
return f'{self.link}[{self.trend_type}]'
class TrendData(models.Model):
class Meta:
verbose_name_plural = 'Trends'
owner = models.ForeignKey(Owner, on_delete=models.CASCADE)
views = models.IntegerField()
views_u = models.IntegerField()
likes = models.IntegerField()
shares = models.IntegerField()
interaction_rate = models.DecimalField(max_digits=20, decimal_places=10)
mean_age = models.IntegerField()
source = models.ForeignKey(TrendSource, on_delete=models.CASCADE)
date_trend = models.DateTimeField()
Source parent model doesn't really help in that case, it's a csv file data was loaded from, so we don't ever reference it.
What I want is, is it possible to calculate sum of views, views_u, likes, shares, interaction_rate if the owner is met for both days (let's say 01.01.19 to 10.01.2019) and if there are 2 of the owners in both days calculate the Sum if not skip and leave it as a simple queryset without summing ALL the values in it, if met then calculate and leave the rest as it is.
I can do it with a python, but i think it is possible to do in django ORM
Django ORM provides a conditional expressions for doing this kind of condition based annotations. You can use Case to annotate the Sum based on the condition you mentioned.
TrendData.objects.filter(owner__trend_type__mnemonic='posts').annotate(
views=Sum(
Case(
When("Your condition here", then=F('views')),
default=0,
output_field=IntegerField(),
)
)
...
)

Tricky Django QuerySet with Complicated ForeignKey Traversals

I am adapting Jim McGaw's e-commerce site from Beginning Django E-Commerce for my client's use. My client sells builds of computers composed of a custom set of parts. The parts that go into a system will change. For instance, the Super Samurai system sold two years from now will have different parts than the one we sell tomorrow. So as we sell each Super Samurai system we need a snapshot of what parts went into the Super Samurai at the moment in time of the sale.
I am having a problem with the QuerySet that copies all of the parts from the table that maps parts to builds (i.e. the parts that go into the Super Samurai)...
class Build(models.Model):
build = models.ForeignKey(PartModel, related_name='+')
part = models.ForeignKey(PartModel, related_name='+')
quantity = models.PositiveSmallIntegerField(default=1)
class Meta:
abstract = True
unique_together = ('build', 'part')
def __unicode__(self):
return self.build.name + ' with ' + str(self.quantity) + ' * ' + \
self.part.family.make.name + ' ' + self.part.name
class BuildPart(Build):
pass
class Meta:
verbose_name = "Build Part"
I need to copy the build parts from the BuildPart table into the OrderBuildPart table...
class OrderBuildPart(Build):
orderItem = models.ForeignKey(OrderItem, unique=False)
class Meta:
verbose_name = "Ordered Build Part"
...so that in the future we know which parts went into so-and-so's build.
McGaw's e-commrece site doesn't allow for items to be bundles of other items. So rather than create some nightmarish scenario of two different tables (and two series of SKUs) for builds and their parts, I wanted a build to be just like any other part...
class PartModel(models.Model):
family = models.ForeignKey(PartFamily)
name = models.CharField("Model Name", max_length=50, unique=True)
slug = models.SlugField(help_text="http://www.Knowele.com/<b>*slug*</b>",
unique=True)
vpn = models.CharField("VPN", help_text="Vendor's Part Number",
max_length=30, blank=True, null=True)
url = models.URLField("URL", blank=True, null=True)
costurl = models.URLField("Cost URL", blank=True, null=True)
cost = models.DecimalField(help_text="How much knowele.com pays", max_digits=9, decimal_places=2, blank=True, null=True)
price = models.DecimalField(help_text="How much a customer pays", max_digits=9, decimal_places=2, blank=True, null=True)
isActive = models.BooleanField(default=True)
isBestseller = models.BooleanField(default=False)
isFeatured = models.BooleanField(default=False)
isBuild = models.BooleanField(default=False)
description = models.TextField(blank=True, null=True)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
buildpart = models.ManyToManyField('self', through='BuildPart',
symmetrical=False, related_name='+')
class Meta:
ordering = ['name']
verbose_name = "Product Model"
def __unicode__(self):
return self.name
def get_absolute_url(self):
from django.core.urlresolvers import reverse
return reverse('productdetail', args=[self.slug])
The buildpart field references the ManyToMany BuildPart table which allows a build to have many parts and for a part to be associated with many builds.
Through adapting McGaw's code I get pretty much what I need until I finalize the PayPal payment and try to record what parts went into the sold builds at the precise moment of sale...
def payment(request):
token = request.POST['token']
payer = request.POST['payer']
result = paypal.do_express_checkout_payment(request, token, payer)
if result['ACK'][0] in ['Success', 'SuccessWithWarning']:
cart = Cart.objects.get(cart_id=get_cart_id(request))
finalOrder = Order()
finalOrder.cart_id = get_cart_id(request)
finalOrder.token = token
finalOrder.corID = result['CORRELATIONID'][0]
finalOrder.payerID = payer
finalOrder.ipAddress = request.META['REMOTE_ADDR']
finalOrder.first = cart.first
finalOrder.last = cart.last
finalOrder.address = cart.address
finalOrder.email = cart.email
finalOrder.transactionID = result['PAYMENTINFO_0_TRANSACTIONID'][0]
finalOrder.status = 'f'
finalOrder.save()
for item in get_cart_items(request):
oi = OrderItem()
oi.cart_id = item.cart_id
oi.quantity = item.quantity
oi.product = item.product
oi.price = item.price()
oi.save()
if item.product.isBuild:
for part in get_build_parts(request, item):
bp = OrderBuildPart()
bp.build = part.build
bp.part = part.part
bp.quantity = part.quantity
bp.orderItem = oi
bp.save()
empty_cart(request)
return render(request, 'payment.html', locals())
Everything seems fine until we hit the get_build_parts function...
def get_build_parts(request, part):
return BuildPart.objects.filter(build__id=part__product__pk)
...where Django's post-mortem complains "NameError at /payment/ global name 'part__product__pk' is not defined"
How do I traverse these complicated relationships so my boss can look up what parts went into each customer's builds?
The value side of the lookup doesn't work the way you think it does. The double-underscore stuff is for the left-hand side only: in effect, it's a hack to get round Python's syntax requirements. On the right-hand side, you pass a normal expression, which can follow object relationships using the standard dot syntax:
return BuildPart.objects.filter(build__id=part.product.pk)
Try BuildPart.objects.filter(build=part__product) instead of what you have.
Also, it looks like your "post mortem" is coming from the webapp actually being used. You should be learning of problems like this from unit tests failing, not from an HTTP call.

django database structure for a gym members workout management

i would like to develop a django management app for a small gym.
we have a list of members
each member could have 1 or more card
each card could have 1 or more workout
each workout could have 1 or more exercise
each exercise has the following fields:
exercise name (can be choose from a list of exercise names),
series number (can be choose from a list of series),
repetition number (can be choose from a list of repetition),
execution mode (can be choose from a list of executions),
rest time (can be choose from a list of executions).
Here is my possibile implementation of models.py:
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Member(models.Model):
#Member data
name = models.CharField(max_length=50)
surname = models.CharField(max_length=50)
#Contact
email = models.EmailField(blank=True, null=True)
phone = models.CharField(max_length=50, blank=True, null=True)
#Body
birthday = models.DateField(blank=True, null=True)
height = models.IntegerField(blank=True, null=True)
weigth = models.IntegerField(blank=True, null=True)
#Trainer notes
trainer = models.ForeignKey(User, limit_choices_to={'is_staff': True, 'is_superuser': False}, blank=True, null=True)
note = models.CharField(max_length=160, blank=True, null=True)
#Registration status
registration = models.DateField(auto_now_add=True)
expiration = models.DateField(blank=True, null=True)
card = models.OneToOneField('Card')
def __str__(self):
return u'%s %s' % (self.surname, self.name)
class Card(models.Model):
number = models.IntegerField()
#Card status
card_creation = models.DateField(auto_now_add=True)
card_expiration = models.DateField(blank=True, null=True)
workout = models.ForeignKey('Workout')
def __str__(self):
return u'%s' % (self.number)
class Workout(models.Model):
number = models.IntegerField()
exercise = models.ForeignKey('Exercise')
def __str__(self):
return u'%s' % (self.number)
class Exercise(models.Model):
name = models.ForeignKey('Name')
series = models.ForeignKey('Serie')
repetitions = models.ForeignKey('Repetition')
executions = models.ForeignKey('Execution', blank=True, null=True)
rest = models.ForeignKey('Rest')
def __str__(self):
return u'%s' % (self.name)
class Name(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return u'%s' % (self.name)
class Serie(models.Model):
serie = models.CharField(max_length=50)
def __str__(self):
return u'%s' % (self.serie)
class Repetition(models.Model):
repetition = models.IntegerField()
def __str__(self):
return u'%s' % (self.repetition)
class Execution(models.Model):
execution = models.CharField(max_length=50)
def __str__(self):
return u'%s' % (self.execution)
class Rest(models.Model):
rest = models.IntegerField()
def __str__(self):
return u'%s' % (self.rest)
I'm not sure if this works as described. Could you please suggest a possible implementation?
At the end I would like to have only one view with all data: member, card, workout... so the staff user can modify the user and the linked workout card) is it possible with admin.py or do I need a custom admin?
That looks pretty good, and like Samidh T said in the comments you should open up a dev server with sqlite and play around with it. See what's working what's not. That's the best thing you could do.
However, since you asked, here are a couple notes to bear in mind:
"each member could have 1 or more card" - Then why is you card field in Memeber a OneToOne? Wouldn't it make more sense to do a ForeignKey (essentially a ManyToOne) field for Card linking it to member?
"each card could have 1 or more workout" - If you have a ForeignKey from card to workout, then you are actually doing the opposite of what you described, you're doing a Many (cards) for One (workout). But maybe this is what you want? Or maybe you actually want it to be a ManyToManyField? Because each workout probably has different cards and vice-versa. I really can't tell what's the best fit here, but it's something you might want to think over.
"each workout could have 1 or more exercise" Same as with the point before.
I see that every field in your exercise is a ForeingKey. Now again - this is isn't a bad choice or idea, but you should consider what this entails, and consider replacing it with a ManyToMany or, if one of the fields is a static list (i.e. you have a limited list of available names that you know won't change in the future drastically) then you can use a CharField with option choices=.
I can't exactly tell you "this is bad", "this is good", it is a very project-subjective thing, but I hope my few tips helped you in some way. In the end, it all boils down to what you need. If you find yourself confused, I suggest reading a little about table relationships in SQL and how they work.
Django is fantastic for realizing complex relationships, but using it without learning a little bit SQL before can be confusing sometimes because you only see the end result and hardly ever look at the tables themselves.

Categories

Resources