I am trying to build a Django App which lets small Stock Clubs easily manage and view data about their club. The issue I have run into is building elegant and logical models. I have not found any literature about a 'good' way to build models, or even a 'good' way to go about starting to plan models, so I thought I would post my models.py here and see what you think.
Do I have them built logically? Is there a better way? I'm not as concerned with the style of the code itself (it will eventually be PEP-8 compliant) as I am with the logic and elegance of the design.
To get a better idea of what the purpose is, here is a very plain-looking example of what the data contained by the models will be generating: http://www.bierfeldt.com/takestock/clubs/1/
Here is a flowchart design of what the models look like currently:
Thank you.
models.py
from django.db import models
####### Stock Models #######
class Stock(models.Model):
'''A stock whose current_price is updated every minute by a cronned script
running on the server. The current_price updating script gets all Stock objects
and runs Google Finance queries on each Stock
Stock has no relation to a particular owner, for that, see StockInstance model below'''
ticker = models.CharField(max_length=200)
current_price = models.DecimalField(max_digits=20, decimal_places=2)
def __unicode__(self):
return str(self.ticker + ">" + str(self.current_price))
class StockInstance(models.Model):
'''A middle-man model which links a Stock model to an owner (see Club model below.)
A single owner may possess multiple instances of a single stock purchased at different times
ex. December 9, 2012 - Owner buys 20 shares of AAPL at $500
December 13, 2012 - Owner buys 15 shares of AAPL at $482'''
owner = models.ForeignKey('Club')
stock = models.ForeignKey(Stock)
def current_price(self):
#Current Price of relevant stock
return self.stock.current_price
shares = models.IntegerField()
purchase_date = models.DateTimeField()
purchase_price = models.DecimalField(max_digits=20, decimal_places=2)
#if is_open is False, the instance is considered a closed position
is_open = models.BooleanField(default=True)
sell_date = models.DateTimeField(blank=True, null=True)
sell_price = models.DecimalField(max_digits=20, decimal_places=2, blank=True, null=True)
def current_value(self):
#Current value of this stock instance
#ex. $200 Current Price * 10 shares = $2000
return (self.current_price() * self.shares)
def purchase_value(self):
#Purchase value of this stock instance
#ex. $195 Purchase Price * 10 shares = $1950
return (self.purchase_price * self.shares)
def percent_gl(self):
#Percent Gained/Lost
#ex. ($2000 Current Value - $1950 Purchase Value) / ($1950 Purchase Value) = .03 (03%) Gained
return ((self.current_value() - self.purchase_value()) / (self.purchase_value()))
def amount_gl(self):
#Dollar Value Gained/Lost
#ex. $2000 Current Value - $1950 Purchase Value = $50 Gained
return (self.current_value() - self.purchase_value())
def total_percentage(self):
#Percent of Club Value (all club assets including cash) which this stock instance comprises
#ex. $2000 Current Value / $10000 Club Total Assests = .20 (20%)
return (self.current_value() / self.owner.current_value())
def __unicode__(self):
return str(str(self.shares) + str(" of ")+ str(self.stock.ticker))
####### Member Models #######
class Member(models.Model):
'''Members may belong to multiple clubs. The Member model has no relation to a club
See MemberInstance model below'''
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __unicode__(self):
return self.name
class MemberInstance(models.Model):
'''A middle-man model which links a Member model to an owner (see Club model below.)
A single member may belong to multiple clubs at the same time
ex. John has 5 shares of "Sandstone Investment Club"
John has 15 shares of "Blackwell Investment Firm"'''
owner = models.ForeignKey('Club')
member = models.ForeignKey(Member)
shares = models.DecimalField(max_digits=20, decimal_places=2)
def total_share_value(self):
#Total Dollar value of all of particular member's shares of a club
#ex. Sandstone Investment Club's Share Price is $20 and John has 5 shares
#ex. cont. $20 * 5 shares = $100 value of John's shares in Sandstone Investment Club
return (self.shares * self.owner.current_price())
def total_share_percentage(self):
#Percent of a club that a particular member owns
#ex. John has $100 of Sandstone Investment, Sandstone Investment is worth $1000
#ex. cont. $100 / $1000 = .10 (10%) John owns 10% of Sandstone Investment's Value
return (float(self.total_share_value()) / float(self.owner.current_value()))
####### Club Models #######
class Club(models.Model):
'''A Stock Club
A club has members (MemberInstance) and buys Stocks (StockInstance).
A note on the real-life purpose of stock clubs: Small-tim individual investors often do not have the
buying power to make powerful stock purchases. A single individual may not be able to buy 50 shares
of a stock priced at $500 each. This individual joins a stock club, possibly with friends, family, or co-workers.
The stock club has a number of shares that each member owns some of. The stock CLUB may own shares of many different
STOCKS, but the club only has ONE stock price--its own.'''
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
cash = models.DecimalField(max_digits=20, decimal_places=2)
def total_shares(self):
#The number of shares of the club that exist
#ex. John has 6 shares of the club; Bob has 4 shares of the club
#ex. cont. The club has (6+4=9) 10 total shares among its members.
shares = 0
for member in self.memberinstance_set.select_related():
shares = shares + member.shares
return shares
def current_value(self):
#The current value of the club
#The current value of each stock instance plus the club's uninvested cash
#ex. $200 from AAPL StockInstance + $400 from GOOG Instance + $20 cash = $620
value = 0
for stock in self.stockinstance_set.select_related():
if stock.is_open == True:
value = value + stock.current_value()
else:
pass
return (self.cash + value)
def current_price(self):
#The club's current share price
#The current value of the club divided by the total number of shares of the club
#ex. $620 Club Current Value / 10 Total Shares = $62 per share
return (self.current_value() / self.total_shares())
def cash_total_percentage(self):
#Percent of club's current value that is uninvested cash
return ((self.cash) / (self.current_value()))
def __unicode__(self):
return self.name
A cursory glance at your models suggests that you've got a mostly good design. A few mentions though.
MemberInstance (and other 'middle-man models') are usually named ClubMember, a name describing both sides of the relationship.
Your 'middle-man models' are examples of ManyToMany Models. You should define ManyToManyField on your models, such as members = ManyToManyField(Member, through='ClubMember') on your Club model.
I'll leave PEP-8 style comments alone as that doesn't seem to be the point of your question, however you should consider doc-strings in your methods instead of multiple single line comments to improve command line docs and introspection.
Also note that select_related should take in the fields that you want django to follow the relations on (as of django 1.5).
I'd also move your Club model higher up in the file so you don't need to reference it via 'Club' and can instead just pass the class name.
Have you read the coding styles section in the django docs?
https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#model-style
Related
I'm trying to make a payment through the ecommerce website I created. The payment successfully went through but the total amount charged is different than what I wanted to charge. For example, I want to charge $29.19 but I get charged $2,918.90. I know it has something to do with the decimal places, but I seem to have it correct; 2 decimal places.
I tried adding decimal to the subtotal, tax, and shipping to get a decimal total, though it came, I still had a wrong total amount.
My cart.py:
def get_total(self):
subtotal = sum(Decimal(item['price']) * int(item['quantity']) for item in self.cart.values())
tax = Decimal('0.10')
tax_price = sum(Decimal(item['price']) * int(item['quantity']) for item in self.cart.values()) * Decimal(tax)
shipping_cost = 5
total = Decimal(subtotal) + Decimal(tax_price) + Decimal(shipping_cost)
return total
My Payment views.py:
#login_required
def CartView(request):
cart = Cart(request)
total = str(cart.get_total())
total = total.replace('.', '')
stripe.api_key = ''
intent = stripe.PaymentIntent.create(
amount=total,
currency='usd',
metadata={'userid': request.user.id}
)
Order class:
total_paid = models.DecimalField(max_digits=5, decimal_places=2)
OrderItem class:
price = models.DecimalField(max_digits=5, decimal_places=2)
If you look at the PaymentIntent object in the Stripe documentation you can see that the amount parameter accepts the price in the smallest currency unit.
Amount intended to be collected by this PaymentIntent. A positive
integer representing how much to charge in the smallest currency unit
(e.g., 100 cents to charge $1.00 or 100 to charge ¥100, a zero-decimal
currency).
If you want to charge $1.00 the amount you pass to the PaymentIntent should be 100. If you want to charge $29.19 the amount you pass should be 2919.
Instead of total = total.replace('.', '') try making a int amount and multiply it by a 100 (or store the prices in the cents beforehand).
Admin wants to add different challenges. Each challenge has a lot of users. each user may have a lot of likes. I want to show the winner of each challenge. For that, I need to get which candidate gets the highest likes. How can I get it? is there any way like .count .?
how can I use that? in which model.
For example:
challenges
1 first_contest
2 second_contest
candidates
id name contest
1 jhon first_contest
2 sara second_contest
3 abi first_contest
candidates likes
id user_id candidate_id
1 1 1
2 2 2
3 1 1
In this case candidate, 1 = Jhon get 2 likes so in the first contest Jhon wins. Also in the second contest, Sara gets 1 like. So I need to show the winner in the first contest. How is that?
models.py:
class Challenge(models.Model):
title = models.CharField(max_length=50)
class Candidates(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.FileField( upload_to="challenge_candidates/",)
def likes_count(self):
return self.likes.all().count()
class CandidateLikes(models.Model):
like = models.CharField(max_length=10)
user =
models.ForeignKey(User,on_delete=models.CASCADE,related_name='candidate_likes')
contest_candidates = models.ForeignKey(Candidates, on_delete=models.CASCADE,
related_name='likes')
Sorry for my poor English. Thank you.
You first need to have a relationship between your CandidatLike model and Challenge model so that you can filter by challenge. A foreign key relation could be sufficient. Then you can add this query to your views
winner = CandidateLikes.objects.filter(challenge="your_challenge_name").order_by("like")
Notice that challenge should exist in your CandidatLike model, since we are filtering by it.
I think you are missing a relationship between Challenge and Candidates. Let's say you would add a challenge field to Candidate:
class Candidates(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
challenge = models.ForeignKey(Challenge, on_delete=models.CASCADE, related_name='candidates')
Then you can query the winner of each challenge with the highest like-count with a subquery like this:
from django.db.models import Count
from django.db.models import OuterRef, Subquery
cadidates = Candidates.objects.annotate(like_count=Count('likes')).filter(challange=OuterRef('pk')).order_by('-like_count')
queryset = Challenge.objects.annotate(winner=Subquery(cadidates.values('owner__username')[:1]))
This will give you a Challenge result query with an annotated winner like:
{'id': 1, 'title': 'Challange 1', 'winner': 'username'}
I have a model in my django app like below:
models.py
class Profit(models.Model):
client = models.ForeignKey(Client, null=True, on_delete=models.CASCADE)
month = models.CharField(max_length=100)
amount = models.IntegerField()
total_profit = models.IntegerField()
Now, what I want to do is that whenever a new instance/object is created for this class, the user puts the month and the amount of profit for that month, But I want that it also calculates the total profit the user got up till the current profit, by adding all the profits that was being added in the past.
For example.
if the user is adding the profit for month April, then it add all the values in the amount field of previously added objects of (March, February, January and so on..) and put it in the field total_profit. So that the user can see how much total_profit he got at each new entry.
My views.py where I am printing the list of profits is given below:
views.py
class ProfitListView(ListView):
model = Profit
template_name = 'client_management_system/profit_detail.html'
context_object_name = 'profits'
# pk=self.kwargs['pk'] is to get the client id/pk from URL
def get_queryset(self):
user = get_object_or_404(Client, pk=self.kwargs['pk'])
return Profit.objects.filter(client=user)
Client is the another model in my models.py to which the Profit class is connected via ForeignKey
I also don't exactly know how to use window functions inside this view.
As stated in the comments one should generally not store things in the database that can be calculated from other data. Since that leads to duplication and then makes it difficult to update data. Although if your data might not change and this is some financial data one might store it anyway for record keeping purposes.
Firstly month as a CharField is not a very suitable field of yours for your schema. As firstly they are not easily ordered, secondly it would be better for you to work with a DateTimeField instead:
class Profit(models.Model):
month = models.CharField(max_length=100) # Remove this
made_on = models.DateTimeField() # A `DateTimeField` is better suited
amount = models.IntegerField()
total_profit = models.IntegerField()
Next since you want to print all the Profit instances along with the total amount you should use a Window function [Django docs] which will be ordered by made_on and we will also use a frame just in case that the made_on is same for two entries:
from django.db.models import F, RowRange, Sum, Window
queryset = Profit.objects.annotate(
total_amount=Window(
expression=Sum('amount'),
order_by=F('made_on').asc(),
frame=RowRange(end=0)
)
)
for profit in queryset:
print(f"Date: {profit.made_on}, Amount: {profit.amount}, Total amount: {profit.total_amount}")
I have a class in my models.py
class Inventory(models.Model):
date = models.DateField(("Date"), default=datetime.now)
product = models.ForeignKey(Product)
stock_in = models.IntegerField()
stock_out = models.IntegerField()
balance = models.IntegerField()
particulars = models.CharField(max_length=250)
Now I want to add some stocks in the balance. Using the stock_in values to add certain numbers to the balance of a specific product in the Inventory class. Using an UpdateView to it, so that I can just Update the stock_in field then adding that value to the balance.
I'm currently using this, I've tried couple of solution in the internet but to no avail.
#property
def total(self):
return self.stock_in + self.balance
There is no 'official' mechanism in Django to do this. Recently, some ideas of adding some official solution to the Django framework were discussed in this thread on the django-developers mailing list. It might serve as an inspiration for what solution is currently best for your case.
Your method works well for simple calculations. If the property gets more expensive to calculate, using #cached_property can help a bit if the value is used multiple times.
You can also rely on the database to compute these values by adding an annotation to the queryset. This requires defining a custom Manager:
class InventoryManager(models.Manager):
def get_queryset(self):
super().get_queryset().annotate(total=F('stock_in') + F('balance'))
class Inventory(models.Model):
date = models.DateField(("Date"), default=datetime.now)
product = models.ForeignKey(Product)
stock_in = models.IntegerField()
stock_out = models.IntegerField()
balance = models.IntegerField()
particulars = models.CharField(max_length=250)
objects = InventoryManager()
This will add a balance attribute to your Inventory model instances if they are retreived using the default manager.
The problem with this approach (like discussed in the linked django-developers thread) is what your expectations are when modals are changed locally.
For example, with the custom manager in place, if I were to change stock_in for a modal, the value of total would still be valid for the value of stock_in at the time of retrieving it from the database:
>> qs = Inventory.objects.filter(date__gte=date(2017, 12, 22))
>> inventory0 = qs[0]
>> print(inventory0.total, inventory0.stock_in, inventory.balance)
100, 50, 50
>> inventory.balance = 100
>> print(inventory0.total, inventory0.stock_in, inventory.balance)
100, 50, 100
Also, an model instance not fetched from the db at all wont have a total attribute:
>> inventory = Inventory(stock_in=20, balance=10)
>> inventory.total
AttributeError: 'Inventory' object has no attribute 'total'
Adding a __getattr__ method to your class might be a solution to this usecase, but will still result in incorrect answers with local changes.
Now I can find each employee's total commissions but I want to sort them in days.
model.py
class Sale(models.Model):
date=models.DateTimeField(auto_now_add=True,blank=True,null=True)
commission=models.DecimalField(max_digits=7,decimal_places=2,blank=True,null=True)
ouremployee=models.ForeignKey(Employee,blank=True,null=True)
My sale object already recorded commission associated with each sale and each employee but now I want to find each employee's total commission with 7 days.
views.py
Sale = Sale.objects.all()
total_commission=0
TotalCommission = Sale.annotate(total_commission=Sum('ouremployee'))
My views.py doesn't work.
Here is my query:
Employee.objects.filter(str(datetime.datetime.now())-'sale__date') > str(datetime.timedelta(days=30))).annotate(total_commission=Sum('sale__commission'))