Loop through objects and change their values - python

I have my model set up so that when a liquor is added to a store, it gives it it's own in-store ID based on the count. I've called this the SPI. The addition function works fine, it properly assigns the correct SPI. But I'm having trouble with the delete function. When an object is deleted from the table, I need it to loop through the remaining objects and set their SPI to one less. However I keep getting an error StoreLiquor matching query does not exist. Lookup parameters were {'StoreLiquorID': 7, 'storeID': <Store: test store>} Am I not doing the query correctly?
The models:
class Store(models.Model):
StoreID = models.AutoField(primary_key=True)
user = models.ManyToManyField(User)
StoreName = models.CharField('Store Name', max_length=30)
(other things here too but probably not necessary for this case)
class StoreLiquor(models.Model):
StoreLiquorID = models.AutoField(primary_key=True)
liquorID = models.ForeignKey(Liquor)
storeID = models.ForeignKey(Store)
StorePrice = models.DecimalField('Store Price', max_digits=5, decimal_places=2)
SPI = models.PositiveIntegerField('SPI', max_length=10)
The view:
def delete(request, liquor_id, store_id):
storeID = Store.objects.get(StoreID=store_id)
StLiquor = StoreLiquor.objects.get(storeID=store_id, StoreLiquorID=liquor_id)
LiqSPI = StLiquor.SPI
SPIcount = StoreLiquor.objects.filter(storeID=store_id).count()
for newSPI in range(LiqSPI, SPIcount):
newStLiquor = StoreLiquor.objects.get(storeID=storeID, StoreLiquorID=newSPI)
newStLiquor.SPI = newSPI-1
StLiquor.delete()
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))

Instead of for loop, use queryset.update with django.db.models.F:
from django.db.models import F
...
StoreLiquor.objects.filter(storeID=storeID, SPI__gt=LiqSPI).update(SPI=F('SPI')-1)

Related

How can I fetch data by joining two tables in django?

Looking for help got stuck at a point, I am new to python and django. There ARE two payments corresponding to one order, one COLLECTION and multiple TRANSFER and i need the payment corresponding to an order whose direction is COLLECTION only NOT transfered yet so that i can initiate TRANSFER against that order
models.py
class Orders(models.Model):
id= models.AutoField(primary_key=True)
payment_gateway_code = models.CharField(max_length=20,choices=[('PAYTM','PAYTM')])
is_active = models.BooleanField(default=True)
class Payments(models.Model):
id = models.AutoField(primary_key=True)
orders = models.ForeignKey(Orders, on_delete=models.CASCADE)
direction = models.CharField(max_length=20,choices=[('COLLECTION','COLLECTION'),
('TRANSFER','TRANSFER')])
settlement_status = models.CharField(max_length=50,blank=True, null=True,choices=[('YES','YES'),
('NO','NO')])
is_active = models.BooleanField(default=True)
qualified_orders = Orders.objects.filter(payment_gateway_code='CASHFREE',
Exists(Payments.objects.filter(order=OuterRef('pk'), direction='COLLECTION',
settlement_status='YES')), ~Exists(Payments.objects.filter(order=OuterRef('pk'),
direction='TRANSFER')))
But above query is not working
What is OuterRef('pk')?
First, I'd suggest changing orders to order.
Then, the query you're trying to achieve will be something like this (Assuming order_id contains the ID of the order):
Paymen.objects.filter(order_id=order_id, direction="COLLECTION")
You can use views.py for that as follows
Models.py
class Orders(models.Model):
id= models.AutoField(primary_key=True)
payment_gateway_code = models.CharField(max_length=20,choices=[('PAYTM','PAYTM')])
is_active = models.BooleanField(default=True)
class Payments(models.Model):
id = models.AutoField(primary_key=True)
orders = models.ForeignKey(Orders, on_delete=models.CASCADE)
direction = models.CharField(max_length=20,related_name="direction",choices=[('COLLECTION','COLLECTION'),
('TRANSFER','TRANSFER')])
settlement_status = models.CharField(max_length=50,blank=True, null=True,choices=[('YES','YES'),
('NO','NO')])
is_active = models.BooleanField(default=True)
views.py
from App.models import orders, payments
#in case if you need objects of order this is for that
def orderfunc():
order = Orders.objects.all()
def paymentfunc():
payment = Payment.objects.all()
# from here you can check for what record you want using conditional operator
#if direction == COLLECTION:
#then do what you need

Not able to access related model data using foreign key in Django

models.py
class products(models.Model):
name = models.CharField(max_length=100)
sku = models.CharField(max_length=50)
vendor = models.CharField(max_length=50)
brand = models.CharField(max_length=50)
price = models.FloatField()
product_status = models.BooleanField()
quantity = models.IntegerField()
def __str__(self):
return self.name
# categories
class categories(models.Model):
category_name = models.CharField(max_length=50)
parent_id = models.IntegerField()
# product categories
class product_categories(models.Model):
product = models.ForeignKey(products, on_delete=models.CASCADE)
category = models.ForeignKey(categories, on_delete=models.CASCADE)
def __str__(self):
return self.category
I can access 'category' table data(inside django shell) using
data = products.objects.all()
data.values('product_categories__category__category_name')
output: <QuerySet [{'product_categories__category__category_name': 'xxxx'}}]>
If I put this(inside django shell)
data.product_categories.category
output: 'QuerySet' object has no attribute 'product_categories'
How do I get a queryset(can be passed to html) which includes data from "categories" table along with the data of "products" table
There are a couple of issues happening here. First, data is a queryset, which is kind of like a list of objects, even though here there's just one object in the list. What you want is to get an attribute off of the item in the list, so you need something like a data.first() to get to that object before you start dotting into its attributes.
Secondly, the way Django handles reverse FK relationships requires that you refer to the FK by the standard name of, in your case, product_categories_set, OR you set your own related_name attribute on the FK. Something like:
# product categories
class product_categories(models.Model):
product = models.ForeignKey(products, on_delete=models.CASCADE, related_name='product_categories')
category = models.ForeignKey(categories, on_delete=models.CASCADE, related_name='product_categories')
def __str__(self):
return self.category
so that you can refer to your product_categories model from both the product and categories using just data.product_categories.
Thirdly, when accessing a reverse FK relationship, just like in point (1) above, you will get a related manager, from which you can get a queryset of items. Thus, to get the category name, you need to indicate which item you want the category name for. Assuming it's just the first item for everything, it would look something like:
data = products.objects.all()
product_category = data.product_categories.all()
category_name = product_category.category.category_name
Of course once you have more data, you'll not always want to just pick the first item, so you'll need to add filtering logic into the query to make sure you get the item you're looking for.
ETA, I do agree with the comment by Jorge above - a MTM would make this a bit simpler and would, in essence, create your product_categories table for you.

How to automatically mark a specific field either False or true after certain time has been elapsed?

I'm creating a Hotel table reservation web app in Django 3.x.x
I have a Table Model with isBooked field that is set to either False or True depending on a table status. Code for this model:
class Table(models.Model):
title = models.CharField(max_length=50, null=True)
t_id = models.IntegerField("Table ID", null=True, unique=True)
isBooked = models.BooleanField('Is Booked', default=False)
chairs_booked = models.IntegerField("Chairs Booked", default=0)
max_chairs = models.IntegerField("Total Chairs", default=0)
max_book = models.IntegerField("Least Bookable", default=0)
chairs_left = models.IntegerField("empty chairs", default=0)
Now, I have another Model called Reservation that stores data for booked Tables. Here is its code:
class Reservation(models.Model):
t_username = models.CharField("Booked By", max_length=100, null=True)
t_email = models.EmailField("Email Address", null=True)
t_phone = models.IntegerField("Phone Number", default=0, null=True)
t_id = models.IntegerField("Table Id", null=True)
booked_date = models.CharField("Check In Date", null=True, max_length=50) # '2020-10-27'
booked_time = models.CharField("Check in Time", null=True, max_length=50) # '7:00'
def _get_checkout_date(self):
t = self.booked_date
return t
checkout_date = property(_get_checkout_date)
def _get_checkout_time(self):
the_time = dt.datetime.strptime(self.booked_time, '%H:%M')
new_time = the_time + dt.timedelta(hours=3)
return new_time.strftime('%H:%M')
checkout_time = property(_get_checkout_time)
at this point, I have a table with following information stored in variables:
booked_time this holds a string for time showing when table was booked e.g. 7:00
checkout_time this holds a string for time showing when table should be vacated/checked-out e.g 10:00
so based on the above variables, I want to automatically mark a table's isBooked property to False after certain time has passed (e.g. 3 hours) since it was booked. How do I pull off such a thing?
if this is bad design ( It smells like one..) how do I automatically mark a table either booked/Free using this information/fields available in their respective models?
If you really need to update such data in database level, a solution would be using cronjobs, but this means to leave Django's domain.
It's not usual such a need, as often the meaning of the information is useful on application level, rather than database level. This means that you should evaluate how much time has passed, or if the passed time is above/below a threshold within a view function or model function.
An elegant solution for this would be to add a property to your model, and remove the is_booked field:
#property
def is_booked(self):
time_treshold_in_hours = 3
booked_datetime = datetime.strptime('{} {}'.format(self.booked_date, self.booked_time), '%Y-%m-%d %H:%M')
booked_deltatime = datetime.now() - booked_datetime
return booked_deltatime.seconds // 3600 > time_treshold_in_hours
and within the view you are evaluating if a table is booked, you can do:
def book_table(request):
# <get table>
if table.is_booked:
return HttpResponse('This table is booked already')
# <do your booking>
I'd also recommend changing the fields booked_date and booked_time to:
booked_on = models.DecimalField()

Django form: Update model values based on user input in one field

I am writing a form to let a user enter a purchase from the template. A couple things need to happen:
the purchase goes to populate a row in the replenishment table
some fields of the replenishment table get updated based on what the user has input
here is what my model look like:
class replenishment(models.Model):
Id = models.CharField(max_length=100, primary_key=True, verbose_name= 'references')
Name = models.CharField(max_length=200)
Quantity = models.FloatField(default=0)
NetAmount = models.FloatField(default=0)
SupplierID = models.CharField(max_length=200)
Supplier = models.CharField(max_length=200)
SellPrice = models.FloatField(default=0)
StockOnOrder = models.FloatField(default=0)
StockOnHand = models.FloatField(default=0)
def __str__(self):
return self.reference
and the form:
class ProcurementOperationRecord(forms.Form)
Id = forms.CharField(required=True)
Quantity = forms.FloatField(required=True)
NetAmount = forms.FloatField(required=True)
Supplier = forms.CharField(required=True)
SellPrice = forms.FloatField(required=True)
I have no clue how to let the user input the values in form and automatically add Quantity to StockOnOrder as well as automatically recognize the SupplierID based on Supplier. At this point I don't know where to start really. At least, is it possible to achieve what I try to do?
First, I've changed some things around and added some comments to what and why I did them.
# models/classes in python are singular AND camel cased (99.9%)
class Supplier(models.Model):
...
# models/classes in python are singular AND camel cased (99.9%)
class Replenishment(models.Model):
# attributes are normally lower case and snake cased (99.9%)
# try not to do this, a CharField??, unless you're using a guid? if so use UUIDField()
# https://docs.djangoproject.com/en/3.1/ref/models/fields/#uuidfield
id = models.CharField(db_column='Id', max_length=100, primary_key=True, verbose_name='references')
name = models.CharField(db_column='Name', max_length=200)
quantity = models.FloatField(db_column='Quantity', default=0)
net_amount = models.FloatField(db_column='NetAmount', default=0)
# deleted your field "Supplier" -- with this change you can join to the other table and get what you need without having to duplicate anything
supplier = models.ForeignKey(Supplier, db_column='SupplierID')
sell_price = models.DecimalField(db_column='SellPrice', default=0, max_digits=6, decimal_places=2) # You're asking for trouble if you keep this as FloatField
stock_on_order = models.IntegerField(db_column='StockOnOrder', default=0) # how can you have ordered a .5 for your stock? changed to IntegerField
stock_on_hand = models.IntegerField(db_column='StockOnHand', default=0) # how can you have a .5 of your stock? changed to IntegerField
class Meta:
db_table = 'replenishment' # try not to do this either.. let django come up with the name.. unless you're using an existing database/table?
...
# models/classes in python are singular AND camel cased (99.9%)
# django has a standard that they normally postfix forms with "Form" at the end of the class (no matter if it's a ModelForm or regular Form)
class ProcurementOperationRecordForm(forms.ModelForm)
class Meta:
model = Replenishment
fields = ('id', 'quantity', 'net_amount', 'supplier', 'sell_price')
# I would remove the "id", the client shouldn't care or know about it..
Now to create and update. (This would live inside a view)
# creating?
form = ProcurementOperationRecordForm(data=request.POST)
if form.is_valid():
form.save()
return redirect(..) or render(..)
# updating?
replenishment = Replenishment.objects.get(id='...something')
form = ProcurementOperationRecordForm(data=request.POST, instance=replenishment)
if form.is_valid():
form.save()
return redirect(..) or render(..)
This is just a general idea. You can try something like this.
First get the user input values of quantity and supplier like this from the valid form.
quantity = form.cleaned_data.get('quantity')
supplier = form.cleaned_data.get('supplier')
Then you can update your replenishment model
replenishment.objects.filter(Supplier=supplier).update(StockOnOrder=quantity)

What is the "instance" being passed to the to_representation function of my ListSerializer?

The goal of this project is to create an API that refreshes hourly with the most up to date betting odds for a list of games that I'll be scraping hourly from the internet. The goal structure for the JSON returned will be each game as the parent object and the nested children will be the top 1 record for each of linesmakers being scraped by updated date. My understanding is that the best way to accomplish this is to modify the to_representation function within the ListSerializer to return the appropriate queryset.
Because I need the game_id of the parent element to grab the children of the appropriate game, I've attempted to pull the game_id out of the data that gets passed. The issue is that this line looks to be populated correctly when I see what it contains through an exception, but when I let the full code run, I get a list index is out of range exception.
For ex.
class OddsMakerListSerializer(serializers.ListSerializer):
def to_representation(self, data):
game = data.all()[0].game_id
#if I put this here it evaluates to 1 which should run the raw sql below correctly
raise Exception(game)
data = OddsMaker.objects.filter(odds_id__in = RawSQL(''' SELECT o.odds_id
FROM gamesbackend_oddsmaker o
INNER JOIN (
SELECT game_id
, oddsmaker
, max(updated_datetime) as last_updated
FROM gamesbackend_oddsmaker
WHERE game_id = %s
GROUP BY game_id
, oddsmaker
) l on o.game_id = l.game_id
and o.oddsmaker = l.oddsmaker
and o.updated_datetime = l.last_updated
''', [game]))
#if I put this here the data appears to be populated correctly and contain the right data
raise Exception(data)
data = [game for game in data]
return data
Now, if I remove these raise Exceptions, I get the list index is out of range. My initial thought was that there's something else that depends on "data" being returned as a list, so I created the list comprehension snippet, but that doesn't resolve the issue.
So, my question is 1) Is there an easier way to accomplish what I'm going for? I'm not using a postgres backend so distinct on isn't available to me. and 2) If not, its not clear to me what instance is that's being passed in or what is expected to be returned. I've consulted the documentation and it looks as though it expects a dictionary and that might be part of the issue, but again the error message references a list. https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior
I appreciate any help in understanding what is going on here in advance.
Edit:
The rest of the serializers:
class OddsMakerSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = OddsMakerListSerializer
model = OddsMaker
fields = ('odds_id','game_id','oddsmaker','home_ml',
'away_ml','home_spread','home_spread_odds',
'away_spread_odds','total','total_over_odds',
'total_under_odds','updated_datetime')
class GameSerializer(serializers.ModelSerializer):
oddsmaker_set = OddsMakerSerializer(many=True, read_only=True)
class Meta:
model = Game
fields = ('game_id','date','sport', 'home_team',
'away_team','home_score', 'away_score',
'home_win','away_win', 'game_completed',
'oddsmaker_set')
models.py:
class Game(models.Model):
game_id = models.AutoField(primary_key=True)
date = models.DateTimeField(null=True)
sport=models.CharField(max_length=256, null=True)
home_team = models.CharField(max_length=256, null=True)
away_team = models.CharField(max_length=256, null=True)
home_score = models.IntegerField(default=0, null=True)
away_score = models.IntegerField(default=0, null=True)
home_win = models.BooleanField(default=0, null=True)
away_win = models.BooleanField(default=0, null=True)
game_completed = models.BooleanField(default=0, null=True)
class OddsMaker(models.Model):
odds_id = models.AutoField(primary_key=True)
game = models.ForeignKey('Game', on_delete = models.CASCADE)
oddsmaker = models.CharField(max_length=256)
home_ml = models.IntegerField(default=999999)
away_ml = models.IntegerField(default=999999)
home_spread = models.FloatField(default=999)
home_spread_odds = models.IntegerField(default=9999)
away_spread_odds = models.IntegerField(default=9999)
total = models.FloatField(default=999)
total_over_odds = models.IntegerField(default=999)
total_under_odds = models.IntegerField(default=999)
updated_datetime = models.DateTimeField(auto_now=True)
views.py:
class GameView(viewsets.ModelViewSet):
queryset = Game.objects.all()
serializer_class = GameSerializer
Thanks
To answer the question in the title:
The instance being passed to the Serializer.to_representation() is the instance you pass when initializing the serializer
queryset = MyModel.objects.all()
Serializer(queryset, many=True)
instance = MyModel.objects.all().first()
Serializer(data)
Usually you don't have to inherit from ListSerializer per se. You can inherit from BaseSerializer and whenever you pass many=True during initialization, it will automatically 'becomeaListSerializer`. You can see this in action here
To answer your problem
from django.db.models import Max
class OddsMakerListSerializer(serializers.ListSerializer):
def to_representation(self, data): # data passed is a queryset of oddsmaker
# Do your filtering here
latest_date = data.aggregate(
latest_date=Max('updated_datetime')
).get('latest_date').date()
latest_records = data.filter(
updated_date_time__year=latest_date.year,
updated_date_time__month=latest_date.month,
updated_date_time__day=latest_date.day
)
return super().to_representation(latest_records)

Categories