Accessing two apps in one view in Django - python

I have a Django project with two apps. The first one, market/models.py, includes a Market class and a Share class, where the latter keeps track of all shares bought or sold on any given market, as follows:
class Market(models.Model):
title = models.CharField(max_length=50, default="")
current_price = models.DecimalField(max_digits=5, decimal_places=2)
description = models.TextField(default="")
shares_yes = models.IntegerField(default=0)
shares_no = models.IntegerField(default=0)
b = models.IntegerField(default=100)
cost_function = models.IntegerField(default=0)
open = models.BooleanField(default=True)
def __str__(self):
return self.title[:50]
def get_absolute_url(self):
return reverse('market_detail', args=[str(self.id)])
class Share(models.Model):
user = models.ForeignKey('users.CustomUser',
on_delete=models.CASCADE,
related_name='user_shares',
default=None)
market = models.ForeignKey(
Market,
on_delete=models.CASCADE,
related_name='market_shares',
default=None)
share = models.IntegerField(default=0)
def __str__(self):
return str(self.share)
def get_absolute_url(self):
return reverse('market_list')
The second app, user/models.py, is to create custom users, as follows:
class CustomUser(AbstractUser):
points = models.IntegerField(default=1000)
What I want to do is this: on clicking a button on a template for a specific market, the code will loop through all users with shares in that market to add/subtract the value of each share in their possession from their total points (subtraction would happen when users own negative shares, meaning they owe shares on account of short-selling). The value of each share at that point is simply the current market price.
Here is what I have at the moment, in markets/views.py (the HttpResponseRedirect at the end simply reloads the page):
def resolve(request, pk):
market = Market.objects.get(pk=pk)
market_users = Share.objects.values('user')
for user in market_users:
target_user = CustomUser.objects.get(username=user)
target_user.points += market.current_price * int(user.share)
market.open = False
market.save()
return HttpResponseRedirect('/' + str(pk))
One problem with this (there might be others) is that target_user ends up being of the form <QuerySet [{'user': 1}, {'user': 1}]>, which means Django throws the error CustomUser matching query does not exist.
Any advice would be greatly appreciated!
UPDATE following Higor's suggestion in the comments:
I tried this, which doesn't throw an error, but it fails to update users points:
def resolve(request, pk):
market = Market.objects.get(pk=pk)
market_users = Share.objects.all()
print(market_users) # gives "<QuerySet [<Share: 10>, <Share: 10>]>"
for user in market_users.iterator():
print(user) # gives "10 10"
if user.market == pk:
target_user = CustomUser.objects.get(username=user)
target_user.points += market.current_price * user.share
target_user.save()
market.open = False
market.save()
return HttpResponseRedirect('/' + str(pk))

What you're doing wrong is the way you access your user inside your Share object.
from django.shortcuts import get_object_or_404
def resolve(request, pk):
# if no Market with this pk raises a 404
market = get_object_or_404(Market, pk=pk)
# get shares for the specific market
shares = Share.objects.filter(market_id=pk).select_related('user')
print(shares) # gives "<QuerySet [<Share: 10>, <Share: 10>]>"
for share in shares:
target_user = CustomUser.objects.get(pk=share.user.pk)
target_user.points += market.current_price * share.share
target_user.save()
market.open = False
market.save()
return HttpResponseRedirect('/' + str(pk))
Instead of hardcode your URL you can use reverse like here

Related

TypeError at /listing: save() missing 1 required positional argument: 'self'

I'm trying to make an auction site and I'm having an issue changing the bid in the Bids model for a particular Auctionlisting object when POST data is submitted.
class Bids(models.Model):
minimum_bid = models.DecimalField(max_digits=8, decimal_places=2, null=False)
allbids = []
bid = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
allbids.append(bid)
numBids = len(allbids)
def __str__(self):
if self.bid == None:
return f"Minimum Bid: ${self.minimum_bid}"
else:
return f"Highest Bid: ${self.bid}"
def save(self, userbid, object):
item = self.objects.get(pk=object)
item.bid = userbid
item.save()
Here is my AuctionListing model:
class AuctionListing(models.Model):
title = models.CharField(max_length=30)
description = models.CharField(max_length=137)
category = models.CharField(max_length=10, choices=Category.CHOICES, default="Men")
image = models.ImageField(upload_to="media")
bid = models.OneToOneField(Bids, on_delete=models.CASCADE, null=True)
def __str__(self):
return f"{self.category}: {self.title}"
Here is how I'm submitting the data in my views:
if request.method == "POST":
if "userbid" in request.POST:
try:
userbid = AuctionListing.objects.get(pk = request.GET.get("listing"))
userbid.bid = Bids.save(userbid=1000,object=object)
userbid.save()
except IntegrityError:
pass
The modeling is quite odd. An AuctionListing refers to a Bids model, but it makes not much sense to do that. In case each AcutionListing has a Bids object and vice-versa, it is more sensical to just add the data to the AuctionListing itself.
Furthermore you do not store the individual bids. If later the auction is closed, how will you know who made the highest bid? Your Bids model also creates a list, and appends the bid field to that, but that is the only item it will contain. The logic in the class is only evaluated once: when the class is constructed, so from the moment the class has been interpreted, the bid and numBids do not make much sense anymore.
The Bids also contain a field minimum_bid, but this is information related to the object people are bidding on, not a bid itself.
A more sensical modeling is that you have an AuctionListing to present objects on which one can bid. Per AuctionListing there can be multiple Bids, one can then determine the lowest bid by annotating, or making queries. In that case the two models are thus defined as:
class AuctionListing(models.Model):
title = models.CharField(max_length=30)
description = models.CharField(max_length=137)
category = models.CharField(max_length=10, choices=Category.CHOICES, default='Men')
image = models.ImageField(upload_to='media')
minimum_bid = models.DecimalField(max_digits=8, decimal_places=2)
def __str__(self):
return f'{self.category}: {self.title}'
def get_largest_bit(self):
return self.bids.order_by('-bid').first()
Then you can define a Bid model with a ForeignKey to the AuctionList: the item that people can bid on:
class Bid(models.Model):
item = models.ForeignKey(
AuctionListing,
on_delete=models.CASCADE,
related_name='bids'
)
bid = models.DecimalField(max_digits=8, decimal_places=2)
In your view, you can then make a bid on the AuctionList item with as primary key pk with:
from django.shortcuts import get_object_or_404
def make_bid(request, pk):
item = get_object_or_404(AuctionList, pk=pk)
if request.method == 'POST':
bid_value = request.POST['bid'] # retrieve the bid value from somewhere
if bid_value < item.minimum_bid:
# do something, report an error
# …
else:
Bid.objects.create(item_id=pk, bid=bid_value)
return redirect('name-of-some-view')
else:
# …
# …

How to create a model field that is an average of all of another foreign key model' field. Ex: avg rating field

I have two models. Fiction model that can be any movie, book, tv series or something similar. I have another model that is a review that contain review like fields for example: reviewer, rating, description.
What I want to do is the following:
Have Two extra fields in the fiction model that are:
number of reviews
average of review ratings
I was able to add them as integer and float fields and then changed them whenever a new review was added, edited, or deleted but there are two issues.
Adding reviews from the admin won't be accounted for
I just feel like that is not the best approach and I feel like there is a more sensible approach for the fields to be automatically filled from the review model.
Here are the two models as well as how I implemented the api views.
fiction models.py
from django.db import models
from stream.models import StreamPlatform
class Fiction(models.Model):
"""
Model that encopasses a Movie, TV Series, book or similar
"""
MOVIE = 1
TV_SERIES = 2
BOOK = 3
PODCAST = 4
TYPE = (
(MOVIE, 'Movie'),
(TV_SERIES, 'TV-Series'),
(BOOK, 'Book'),
(PODCAST, 'Podcast')
)
title = models.CharField(max_length=50)
description = models.CharField(max_length=200)
active = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
avg_rating = models.FloatField(default=0)
number_rating = models.IntegerField(default=0)
platform = models.ForeignKey(
StreamPlatform,
on_delete=models.SET_NULL,
related_name='fictions',
null = True
)
type = models.PositiveSmallIntegerField(
choices = TYPE,
default = MOVIE
)
def __str__(self):
return self.title
Review models.py
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.contrib.auth.models import User
from fiction.models import Fiction
class Review(models.Model):
"""
model for fiction reviews from users
"""
reviewer = models.ForeignKey(User, on_delete=models.CASCADE)
rating = models.PositiveSmallIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
fiction = models.ForeignKey(Fiction, on_delete=models.CASCADE, related_name="reviews")
description = models.CharField(max_length=200, null = True, blank =True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.rating) + " | " + str(self.fiction)
class Meta:
unique_together = ['reviewer', 'fiction']
ordering = ['-created']
review views.py
from watchmate.permissions import IsAdminOrReadOnly, IsOwnerOrReadOnly
from rest_framework import generics, mixins
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from django.db.utils import IntegrityError
from fiction.models import Fiction
from .models import Review
from .serializers import ReviewSerializer
class ReviewList(generics.ListCreateAPIView):
permission_classes = [IsAdminOrReadOnly]
# queryset = Review.objects.all()
serializer_class = ReviewSerializer
def get_queryset(self):
pk = self.kwargs['pk']
return Review.objects.filter(fiction=pk)
def perform_create(self, serializer):
pk = self.kwargs.get('pk')
fiction = Fiction.objects.get(pk=pk)
reviewer = self.request.user
# check if user has already reviewd this fiction
# review = Review.objects.filter(fiction=fiction, reviewer=reviewer)
# if review.exists():
# raise ValidationError("You have already reviewed this fiction")
try:
serializer.save(fiction=fiction, reviewer=reviewer)
if fiction.number_rating == 0:
fiction.avg_rating = serializer.validated_data['rating']
else:
fiction.avg_rating = (serializer.validated_data['rating']+fiction.avg_rating)/2
fiction.number_rating += 1
fiction.save()
except IntegrityError:
raise ValidationError("You have already reviewed this fiction")
class ReviewDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer
permission_classes = [IsOwnerOrReadOnly]
def perform_update(self, serializer):
instance = self.get_object()
pk = self.kwargs.get('fiction_pk')
fiction = Fiction.objects.get(pk=pk)
# calculate overall rating sums
total_rating = fiction.avg_rating*fiction.number_rating
# subtract old rating
total_rating -= instance.rating
# add new rating
total_rating += serializer.validated_data['rating']
# calculate new fiction avg rating and save it
fiction.avg_rating = total_rating/fiction.number_rating
fiction.save()
super().perform_update(serializer)
def perform_destroy(self, instance):
instance = self.get_object()
pk = self.kwargs.get('fiction_pk')
fiction = Fiction.objects.get(pk=pk)
# calculate overall rating sums
total_rating = fiction.avg_rating*fiction.number_rating
# subtract old rating
total_rating -= instance.rating
# decrease fiction reviews by one
fiction.number_rating -= 1
# calculate new fiction avg rating and save it
fiction.avg_rating = total_rating/fiction.number_rating
fiction.save()
super().perform_destroy(instance)
The logic for adding a new reviews is not 100% correct but never mind it. I just want to know how to be able to implement this logic at models and field level so it can also be possible to add reviews using admin and still be able to see changes
As said in Willem's comment, you can use #property. Here is an example from the official documentation:
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
#property
def full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
In case anyone wanted a solution to such a problem that is how I solved mine by adding a property to method just like #Willem Van Onsem and #Mhamed Bendenia said.
#property
def number_of_ratings(self):
return self.reviews.count()
#property
def average_rating(self):
return self.reviews.aggregate(Avg('rating'))['rating__avg']
This way I dont have to write the logic myself in every view

django.db.utils.IntegrityError: UNIQUE constraint failed: auctions_bids.item_id

I building the auctions website and have this problem:
UNIQUE constraint failed: auctions_bids.item_id
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.db.models.deletion import CASCADE
from django.db.models.fields import EmailField, NOT_PROVIDED
class User(AbstractUser):
pass
class Listings(models.Model):
title = models.CharField(max_length=64)
description = models.TextField(max_length=250)
price = models.FloatField()
image_url = models.CharField(max_length=150)
category = models.ForeignKey(Categories, on_delete=models.CASCADE)
listed_by = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"{self.title}" #{self.description} {self.price} {self.image_url} {self.category}"
class Bids(models.Model):
item = models.OneToOneField(Listings, on_delete=models.CASCADE)
bid = models.FloatField(blank=True)
user_bid = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
def __str__(self):
return f"{self.item} {self.bid} {self.user_bid}"
views.py
def placebid(request, id_product):
if request.method == "POST":
item_list = Listings.objects.get(pk=id_product)
bid = request.POST['bid']
user = request.user
Bids.objects.update_or_create(item=item_list, bid=bid, user_bid=user)
return redirect(reverse("listings", args=(id,)))
When I try to update the new bid, this appears the above error, the only way to delete the old object and create a new object, I want to update the new bid if the object exists and create if the object doesn't exist by using update_or_create
I think I had wrong in setting parameter item values of the Bids model, but I don't know how to fix them! Any advice for me, Thanks so much !!!
You are filtering on the bid and user_bid as well. This thus means that it will only update if the item, bid and userbid are the same. But likely you want to update from the moment item is the same, so you should use the defaults=… parameter of the .update_or_create(…) method [Django-doc]:
from django.shortcuts import redirect
def placebid(request, id_product):
if request.method == 'POST':
bid = request.POST['bid']
user = request.user
Bids.objects.update_or_create(
item_id=id_product
defaults={'bid': bid, 'user_bid': user}
)
return redirect('listings', id)
Django will thus check if it can find a Bids object for which item_id is id_product. If it can, it will update bid and user_bid as specified in the defaults, otherwise it will create a Bids object with these defaults.
Note: normally a Django model is given a singular name, so Bid instead of Bids.

Connecting Two Models and Only Displaying in Template When they have a relationship

I'm working on a site in Django where I have two models (players and seasons). I would like to display the players on a season page, but only when they are part of that season. Currently, this is what I have in my models file:
models.py
from django.db import models
class Player(models.Model):
pid = models.IntegerField(primary_key=True)
firstname = models.CharField(max_length=50)
lastname = models.CharField(max_length=50)
birthdate = models.DateField()
occupation = models.CharField(max_length=50)
city = models.CharField(max_length=50)
state = models.CharField(max_length=2)
def __str__(self):
name = self.firstname + " " + self.lastname
return name
class Season(models.Model):
sid = models.IntegerField(primary_key=True)
seasonname = models.CharField(max_length=50)
location = models.CharField(max_length=50)
#fsd is film start date
fsd = models.DateField()
#fed is film end date
fed = models.DateField()
#asd is air start date
asd = models.DateField()
#aed is air end date
aed = models.DateField()
def __str__(self):
return self.seasonname
class PxS(models.Model):
# Do I need a primary key on this? PROBABLY -- One to many relationship: one player, potential multiple seaons
pid = models.ForeignKey('Player', on_delete = models.CASCADE,)
sid = models.ForeignKey('Season', on_delete = models.CASCADE,)
# position they finished in
finishposition = models.IntegerField()
# total number of players that season
totalpositions = models.IntegerField()
def __str__(self):
name = "Player: " + str(self.pid) + " | Season: " + str(self.sid)
return name
Here is my views file for reference:
views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic import ListView
from .models import Player, Season, PxS
def home(request):
seasons = Season.objects.order_by('sid')
return render(request, 'webapp/home.html', {'seasons': seasons})
def player(request, pk):
player = get_object_or_404(Player, pk=pk)
return render(request, 'webapp/player.html', {'player': player})
def season(request, pk):
season = get_object_or_404(Season, pk=pk)
return render(
request,
'webapp/season.html',
{'season': season, 'players': Player.objects.all()}
)
def seasons(request):
seasons = Season.objects.order_by('sid')
return render(request, 'webapp/seasons.html', {'seasons': seasons})
Currently, all players display on all season pages. I just can't figure out how to limit them. I've created a PxS model to link players with seasons based on foreign keys (pid and sid) but not sure how to implement them into the view. Am I missing something super obvious? Also, I believe it's a one to many relationship, because one player can be on multiple seasons. Is my thinking on this correct? Any insight is greatly appreciated.
PxS is the through table in a many-to-many relationship. You should define that relationship explicitly:
class Season(models.Model):
...
players = models.ManyToManyField('Player', through='PxS')
Now, in your season view, rather than sending all players explicitly to the template, you can just send the season; then when you iterate through seasons you can just use s.players.all to get the players for that season.
(Note, you shouldn't set primary keys explicitly unless you have a very good reason. Django automatically allocates an id field as the pk, so your PxS model does have one; the only thing you've done by defining the sid and pid pks explicitly is a) renaming them and b) disabling the autoincrement, which you certainly shouldn't do.)

hidden form to save aggregated values to model

I am creating a small django invoicing system for my first python project and I am stuck on this rather large issue handling data. I have a hierarchy of models that I use to easily input the data from paper. I have "services", which are migrated to "invoice", which are then migrated to "Bill Period" that the user will view all of their services provided for that month. The code will explain it better. What I need is a final model with an "Agency To Bill" and "Total" fields with a "Paid" booleanfield so I can keep track of payments and use these value with django-authorizenet. Using models is still new for me, so I'm stumped on how to do this. I am thinking that when the user views the invoice-detail page, a hidden form will be submitted with the cost values added together.
# models.py tiers
# compiled invoice with sum of all "cost" in
# bill period would be the last step
class Service(models.Model):
name_of_service = models.CharField(max_length="50")
cost = models.CharField(max_length="50")
def __unicode__(self):
return unicode(self.name_of_service)
class Bill_Period(models.Model):
start_date = models.DateTimeField(blank=True, null=True)
end_date = models.DateTimeField(blank=True, null=True)
def __unicode__(self):
return unicode(self.start_date)
class invoice(models.Model):
date_of_service = models.DateTimeField(default=timezone.now(),blank=True)
agency_to_bill = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True)
patient = models.ForeignKey('users.Patient', blank=True)
therapy_completed = models.ForeignKey('Service', blank=True)
def __unicode__(self):
return unicode(self.agency_to_bill)
class compiled_invoice(models.Model):
# month, total, paid
# views.py i use to handle the information
# error message "Relation fields do not support nested lookups"
def InvoiceDetail(request, month, year):
current_user = request.user
invoice_detail = invoice.objects.filter(date_of_service__year=year,
date_of_service__month=month,
agency_to_bill=current_user)
c = invoice.objects.filter(paid=False,
date_of_service__year=year,
date_of_service__month=month,
agency_to_bill=current_user)
total = invoice.objects.filter(therapy_completed__Service__cost__isnull=True).aggregate(Sum('cost'))
return render_to_response('invoice-detail.html', {'invoice_detail': invoice_detail,
'current_user': current_user,
'month': month,
})
Any help/advice would be appreciated, because at this point I'm stuck. This hidden form is my last ditch effort.

Categories

Resources