Problem with save method overriding attribute value - django

I have a model called 'Trip' with a Foreign Key to 'Destination'. The Destination model specifies a maximum number of passengers in it's 'max_passengers' attribute.
Trip
class Trip(models.Model):
destination = models.ForeignKey(
Destination,
null=True,
blank=False,
on_delete=models.SET_NULL,
related_name="trips",
)
date = models.DateTimeField()
seats_available = models.IntegerField(
null=False, blank=False, editable=False
)
trip_ref = models.CharField(
max_length=32, null=True, editable=True, blank=True
)
Destination
class Destination(Product):
max_passengers = models.IntegerField(null=True, blank=True)
duration = models.CharField(max_length=20, blank=True)
addons = models.ManyToManyField(AddOn)
min_medical_threshold = models.IntegerField(
default=0, null=False, blank=False
)
def __str__(self):
return self.name
Back in the Trip model, I am overriding the model save method, so that when a trip object is created, the 'seats_available' for that instance is set to the 'max_passengers' of the related destination:
def save(self, *args, **kwargs):
if not self.trip_ref:
date = (self.date).strftime("%m%d-%y")
self.trip_ref = self.destination.pk + "-" + date
if not self.seats_available:
self.seats_available = self.destination.max_passengers
super().save(*args, **kwargs)
I have additional models for Bookings and Passengers. When a booking is created, a post_save signal is sent, calling the model method on my Trips model, def update_seats_avaialable():
def update_seats_available(self):
reservations = (
self.bookings.aggregate(num_passengers=Count("passengers"))
["num_passengers"]
)
self.seats_available = self.destination.max_passengers - reservations
self.save() <---- PROBLEM
THe problem is when all seats are finally taken ie. the passenger count = max_passengers and seats availabe = 0. When self.save triggers the save method, if not self.seats_available = true and so this line of code is run: self.seats_available = self.destination.max_passengers which sets the seats available back to where it started. Is there another way of initialising the model object? My understanding is init method isnt advised...

Okay I came up with a solution:
def update_seats_available(self):
reservations = (
self.bookings.aggregate(num_passengers=Count("passengers"))
["num_passengers"])
self.save(reservations=reservations)
def save(self, *args, **kwargs):
if not self.trip_ref:
date = (self.date).strftime("%m%d-%y")
self.trip_ref = self.destination.pk + "-" + date
reservations = kwargs.pop('reservations', 0)
self.seats_available = self.destination.max_passengers - reservations
super().save(*args, **kwargs)
Here I'm passing the value of reservation to the save method and doing the calculation there instead.

Related

Django: Create new model attributes from current model variables

As I am new to Django I am practicing building a money-saving app. In the app, I want to create model attributes from my current model input. The user-created model fields look like this:
class Items(models.Model):
user = models.ForeignKey(Profile, on_delete=models.CASCADE, null=True, blank=True)
item_name = models.CharField(max_length=200, null=False, blank=False, default='Enter name')
item_category = models.ForeignKey(Categories, null=True, blank=True, on_delete=models.SET_NULL)
item_created_at = models.DateTimeField(auto_now_add=True, null=True, blank=False)
item_start_date = models.DateField(null=True, blank=False)
item_end_date = models.DateField(null=True, blank=False)
item_purchase_price = models.FloatField(null=True, blank=False)
item_rest_value = models.FloatField(null=True, blank=False)
def __str__(self):
return self.item_name
Using four of these fields (item_start_date, item_end_date, item_purchase_price, item_rest_value) I want to calculate several things.
Save goal (purchase price - rest value)
Save period in days (end date - start date)
Days passed (end_date - current_date)
I tried this doing the below:
def __init__(self, item_name, item_start_date, item_end_date, item_purchase_price, item_rest_value):
self.item_name = item_name
self.item_start_date = item_start_date
self.item_end_date = item_end_date
self.item_purchase_price = item_purchase_price
self.item_rest_value = item_rest_value
def get_saving_goal(self, item_purchase_price, item_rest_value):
return self.item_purchase_price - self.item_rest_value
def get_date_delta(self, item_end_date, item_start_date):
return self.item_end_date - self.item_start_date
def get_days_passed(self, ):
from datetime import date
today = date.today()
return today - self.item_start_date ## probably will need something like a datediff function
Question 1: However, when I add these methods below the model fields, it shows this error. How do I solve this?
init() takes 6 positional arguments but 10 were given
Question 2: Beside the error, I am wondering how I can use these methods as a field. In other words, how can I use the method's outcomes in the same way as the model fields?
In the end I solved it like this (overwriting the save() function):
def save(self, *args, **kwargs):
self.item_saving_goal = self.item_purchase_price - self.item_rest_value
self.item_date_delta = (self.item_end_date.year - self.item_start_date.year) * 12 + ( self.item_end_date.month - self.item_start_date.month )
self.item_days_passed = (date.today().year - self.item_start_date.year) * 12 + ( date.today().month - self.item_start_date.month )
self.item_currently_saved = self.item_saving_goal * (self.item_days_passed / self.item_date_delta )
self.item_percentage_saved = self.item_currently_saved / self.item_saving_goal * 100
self.item_monthly_saving = self.item_saving_goal / self.item_date_delta
super().save(*args, **kwargs)

Django: count every same value in a object.filter

I have a CourseEmailPersonEven model that has this:
class CourseEmailPersonEvent(models.Model):
uid = models.UUIDField(primary_key=True, default=uuid.uuid4)
action = models.CharField(max_length=32)
is_filtered = models.BooleanField(default=False)
user_agent = models.CharField(max_length=255, null=True, blank=True)
ip = models.GenericIPAddressField(null=True, blank=True)
ip_address = models.ForeignKey(Ip, on_delete=models.SET_NULL, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
course_email_person = models.ForeignKey(
CourseEmailPerson, related_name="events", on_delete=models.CASCADE
)
In a viewset (retrieve), i'm getting CourseEmailPersonEvent's informations about one single account. But I wanna add the amount of same "ip_address" used from every accounts (even mine). And I don't know what to do:
class UtilsViewSet(viewsets.ViewSet):
#action(detail=False, methods=["get"], url_path="emails/events/ips")
def emails_events_ip(self, request, *args, **kwargs):
account = get_object_or_404(models.Account, uid=self.kwargs["account_pk"])
ips_events_count = (
models.CourseEmailPersonEvent.objects.filter(
course_email_person__account=account,
action="open",
ip_address__isnull=False,
)
.values("ip_address")
.annotate(Count("ip_address"))
.order_by("-ip_address__count")
)
return Response(ips_events_count)
Here the result I wanna have in final: (to add that "ip_address_used_all_account" line)
[
{
"ip_address": "4ead446d-28c5-4641-b44d-f1e3aa7d26f8",
"ip_address__count": 1,
"ip_address_used_all_account: 2,
}
]
How large is this table? How heavily used is this view?
The brute-force approach is that once you have established which ip object you are interested in, do a separate query to count the uses. If I've understood your model right,
ip_address_used_all_account = CourseEmailPersonEvent.filter( ip_address = ip
).count()
Unless there are obvious performance issues with this approach, just keep it simple!
Try this:
class UtilsViewSet(viewsets.ViewSet):
#action(detail=False, methods=["get"], url_path="emails/events/ips")
def emails_events_ip(self, request, *args, **kwargs):
account = get_object_or_404(models.Account, uid=self.kwargs["account_pk"])
ips = models.CourseEmailPersonEvent.objects.filter(course_email_person__account=account, action="open", ip_address__isnull=False).values_list("ip_address")
ips_events_count = models.CourseEmailPersonEvent.objects.filter(ip_address__in=ips).values('ip_address').annotate(Count('ip_address'))
return Response(ips_events_count)

django.db.utils.OperationalError: no such table: store_product

When I run makemigrations error is occurred.
Here is models.py
def unique_order_id():
not_unique = True
while not_unique:
uo_id = random.randint(1000000000, 9999999999)
if not Order.objects.filter(order_id=uo_id):
not_unique = False
return uo_id
class Product(models.Model):
#product code, name, category, unit price, current stock
product_code = models.IntegerField(unique=True)
name = models.CharField(max_length=100)
category = models.CharField(max_length=100)
unit_price = models.FloatField()
current_stock = models.IntegerField()
def __str__(self):
return self.name
class Order(models.Model):
order_id = models.IntegerField(unique=True,
default=unique_order_id)
customer_name = models.CharField(max_length=100)
phone = models.CharField(max_length=14)
email = models.EmailField()
qr_code = models.ImageField(upload_to='qr_codes', blank=True)
def __str__(self):
return self.customer_name
#override ths save method for creating qrcode based on fields on this model.
def save(self, *args, **kwargs):
qr_info = "Invoice No : "+ str(self.order_id)+" Name : "+self.customer_name +" Phone : "+str(self.phone)+ " Email : "+ self.email
qrcode_img = qrcode.make(qr_info)
#canvas = Image.new('RGB', (290, 290), 'white')
canvas = Image.new('RGB', (qrcode_img.pixel_size, qrcode_img.pixel_size), 'white')
canvas.paste(qrcode_img)
fname = f'qr_code-{self.customer_name}.png'
buffer = BytesIO()
canvas.save(buffer,'PNG')
self.qr_code.save(fname, File(buffer), save=False)
canvas.close()
super().save(*args, **kwargs)
class OrderItem(models.Model):
qty = models.IntegerField(default=0)
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True)
#this will return total price of product(unit_price*quntity)
#property
def get_total(self):
total = self.product.unit_price * self.qty
return total
This is forms.py codes
class ItemSelectForm(forms.Form):
p_name = forms.ChoiceField(label='Select Product',choices=list ((obj.product_code,obj.name) for obj in Product.objects.all()))
qty = forms.IntegerField()
#function for checking product avaiability
def clean_qty(self):
data = self.cleaned_data['qty']
product_code = self.cleaned_data['p_name']
product = Product.objects.get(product_code = product_code)
if data > product.current_stock:
raise forms.ValidationError(('Insufficient Stock'), code='ins_stock')
# Always return a value to use as the new cleaned data, even if
# this method didn't change it.
return data
This code works fine on my environment, but when i run this on another environment and run makamigrations this error shows up. After searching online, my understanding is, the p_name field in the form is causing the error, the query is running even before creating the table.
p_name = forms.ChoiceField(label='Select Product',choices=list ((obj.product_code,obj.name) for obj in Product.objects.all()))
How can i get out of this situation and solve the error !
You are executing a query when the p_name field on the ItemSelectForm is initialised, this initialisation happens when your application is parsed/loaded/started. Executing queries at start-up of your application is bad practice and in this case preventing you from running migrations as the query you are running is for a model that has not been migrated.
Use a ModelChoiceField instead and pass it a queryset, this queryset will not be executed on start-up and should alleviate this issue
class ItemSelectForm(forms.Form):
product = forms.ModelChoiceField(Product.objects.all(), label='Select Product')
qty = forms.IntegerField()
#function for checking product availability
def clean_qty(self):
data = self.cleaned_data['qty']
product = self.cleaned_data['product']
if data > product.current_stock:
raise forms.ValidationError(('Insufficient Stock'), code='ins_stock')
# Always return a value to use as the new cleaned data, even if
# this method didn't change it.
return data

How to write and retrieve data from ManyToManyField inside overrided save() method?

I need to write any data to ManyToManyField via Model's form in the template, but i get an error like "... needs to have a value for field "id" before this many-to-many relationship can be used.". It shows when I try to use self.service("service" is my ManyToManyField) in my overrided save() method. I know that ManyToManyField is not basic field and it returns something like queryset, but how can i manipulate data inside save() method, because "self.service" doesn't work.
# models.py
class Appointments(models.Model):
name = models.CharField(max_length=200, db_index=True, verbose_name='Имя, фамилия')
tel = models.CharField(max_length=200, db_index=True, verbose_name='Номер телефона')
e_mail = models.CharField(max_length=200, blank=True, db_index=True, verbose_name='E-mail')
car = models.ForeignKey('Cars', null=True, on_delete=models.PROTECT, verbose_name='Тип автомобиля')
num_car = models.CharField(max_length=200, null=True, db_index=True, verbose_name='Гос.номер автомобиля')
**service = models.ManyToManyField(Services, verbose_name='Тип услуги')**
date_created = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Дата публикации заявки')
date_service = models.DateField(db_index=True, verbose_name='Дата')
time_service = models.TimeField(db_index=True, help_text="Введите время в таком формате: 15:00", verbose_name='Время')
price = models.CharField(max_length=50, db_index=True, null=True, verbose_name='Цена')
def save(self, *args, **kwargs):
for i in Services_prices.objects.all():
ccar = i.car
sservice = i.service
for d in self.service:
if self.car == ccar and d == sservice:
self.price = i.price
break
elif ccar == None and d == sservice:
self.price = i.price
break
super().save(*args, **kwargs)
# forms.py
class AppointmentForm(forms.ModelForm):
service = forms.ModelMultipleChoiceField(queryset=Services.objects.all(), required=False, widget=forms.CheckboxSelectMultiple())
class Meta:
model = Appointments
fields = ('name', 'tel', 'e_mail', 'car', 'num_car', 'service', 'date_service', 'time_service')
In order to have a many_to_many relation between two objects, you need primary keys of the both objects. Before calling super's save, your model does not have a primary key yet.
In your overriden save method, call super first, (e.g.super().save(*args, **kwargs)) then do your stuff, then save again.

Django: object disappearing from model

I have a simple django model with an integer field status:
from django.db import models
class MyObject(models.Model):
dateDownloaded = models.DateField(null=True)
dateShared = models.DateTimeField(null=False)
sharedBy = models.CharField(max_length=100, null=False)
sharedTo = models.CharField(max_length=100, null=False)
token = models.CharField(max_length=200, null=False)
status = models.IntegerField(null=False, default=0)
def save(self, *args, **kwargs):
if not self.pk: # First time saved
self.status = 0
self.token = get_random_string(length=32)
super(MyObject, self).save(*args, **kwargs)
I can add objects to my model and I have a simple helper that counts the number of created objects. Now I also have a call that does the following:
def resetStatus(request):
myObjects = MyObject.objects.all()
for myObject in myObjects:
myObject.status = 0
myObject.save()
return HttpResponse("reset done")
Issue is, after calling this, from time to time all the objects from my database have disappeared. Maybe I have done something wrong with my objects in between but I have no idea what it could be. How can I go about debugging this ?