Django: Create new model attributes from current model variables - django

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)

Related

Problem with save method overriding attribute value

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.

Django computed column with lookup from same table and if class

I am new to programming. I am trying to create a project management site. I created a model as follows:
class Boqmodel(models.Model):
code = models.IntegerField(unique=True)
building = models.ForeignKey(building, on_delete=models.SET_NULL, null=True)
level = models.ForeignKey(level, on_delete=models.SET_NULL, null=True)
activity = models.ForeignKey(activity, on_delete=models.SET_NULL, null=True)
subactivity = models.ForeignKey(sub_activity, on_delete=models.SET_NULL, null=True)
duration = models.IntegerField()
linkactivity = models.IntegerFeild(null=True) #contains code (same as code field) which this specific code is linked to
linktype = models.CharField(max_length=300, null=True)# only two choices start or finish
linkduration = models.IntegerField(null=True)
plannedstart = models.DateField()
plannedfinish = models.DateField()
The problem is I need my planned start as a computed column. The planned column should be as follows:
if linkactivity is null, then it should take a default value 01-01-2019
if else then it should look the linkactivity in code field and then
if linktype is start, then it should specify the start date of code activity +duration
or if linktype is finish plannedfinish + duration
Example
First entry:
code=1
building=A-1
Level=L-1
Activity=Activity-1
Subactivity-Subactivity-1
duration=2
linkactivity=null
linktype=null
linkduration=null
planned start=01-01-2019(as linkactivity=null)
plannedfinish=03-01-2019(planned start+duration)
Second entry:
code=2
building=A-1
Level=L-1
Activity=Activity-2
Subactivity-Subactivity-2
duration=3
linkactivity=1
linktype=start
linkduration=1
planned start=02-01-2019(as linkactivity=1,it searches code1 ,as linktype=start,it searches startdate of code 1 it is 01-01-2019 ; finally 01-01-2019+link duration(1)=02-01-2019)
plannedfinish=05-01-2019(planned start+duration)
Any help would be really appreciated
When you call Model.objects.create() method, the django ORM implicitly calls the Model.save() method inside your model. Since, you are inheriting BoqModel from models.Model and there is no explicit BoqModel.save() method in your BoqModel, Model.objects.create() method calls the save() method from the parent class. Inside Model.save() method you can customize your model save behavior. In order to achieve your intended action, override the save() method as follows -
class Boqmodel(models.Model):
code = models.IntegerField()
building = models.ForeignKey(building, on_delete=models.SET_NULL, null=True)
level = models.ForeignKey(level, on_delete=models.SET_NULL, null=True)
activity = models.ForeignKey(activity, on_delete=models.SET_NULL, null=True)
subactivity = models.ForeignKey(sub_activity, on_delete=models.SET_NULL, null=True)
duration = models.IntegerField()
linkactivity = models.IntegerFeild(null=True) #contains code (same as code field) which this specific code is linked to
linktype = models.CharField(max_length=300, null=True)# only two choices start or finish
linkduration = models.IntegerField(null=True)
plannedstart = models.DateField()
plannedfinish = models.DateField()
def save(self, *args, **kwargs):
if self.linkactivity is None:
self.plannedstart = datetime.date(year=2019, month=1, day=1)
else:
boq = BoqModel.objects.get(code=self.linkactivity)
if self.linktype is not None:
if self.linktype == "start":
self.plannedstart = boq.plannedstart + datetime.timedelta(days=self.linkduration)
elif self.linktype == "finish":
self.plannedstart = boq.plannedfinish + datetime.timdelta(days=self.linkduration)
else:
self.plannedstart = datetime.date(year=2019, month=1, day=1)
else:
self.plannedstart = datetime.date(year=2019, month=1, day=1)
self.plannedfinish = self.plannedstart + datetime.tiemdelta(days=self.linkduration)
super(BoqModel, self).save(*args, **kwargs)
The above is not optimized. But you get the general idea of how to do it. Also learn more about overriding predefined model methdos here.

Django raw query giving same result on all models

I have 3 models Product, Photo, and ProductLikeDilike. I am performing left outer join on all the 3 models. First I am joining Product with Photo and then the resultant table(temp) I am joining with ProductLikeDilike. Below is the raw sql.
Note: olx is the name of django app.
data = Product.objects.raw('select * from (select
olx_product.id,olx_product.name,olx_photo.file,olx_photo.cover_photo_flag
from olx_product left outer join olx_photo on
(olx_product.id=olx_photo.reference_id_id) where
olx_photo.cover_photo_flag="yes" or olx_photo.cover_photo_flag is null) as
temp left outer join olx_productlikedislike on
(temp.id=olx_productlikedislike.product_id_id and
olx_productlikedislike.product_liked_by_id_id=2)')
for x in data:
print(x.name)
What I want to understand that when I use any of the above 3 models to run the raw sql why I am getting the same result i.e.
When I do
data = Product.objects.raw('select *.....')
for x in data:
print(x.name)
or
data = Photo.objects.raw('select *......')
for x in data:
print(x.name)
or
data = ProductLikeDislike.raw('select *.....')
for x in data:
print(x.name)
I am getting the same result. Why?
Please help me to understand this.
Below is the models.py file
from django.db import models
from django.urls import reverse
from django.dispatch import receiver
from django.contrib.auth.models import User
class Product(models.Model):
category = models.ForeignKey(Category ,on_delete=models.CASCADE)
name = models.CharField(max_length = 200, db_index = True)
slug = models.SlugField(max_length = 200, db_index = True)
description = models.TextField(blank = True)
price = models.DecimalField(max_digits = 10, decimal_places = 2 )#Not used FloatField to avoid rounding issues
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
contact= models.BigIntegerField(default=None,blank=True, null=True)
created_by = models.CharField(max_length = 200, default=None,blank=True, null=True)
uploaded_by_id = models.IntegerField(default=0)
status = models.IntegerField(default=0) # 0-->Active,1-->Inactive
mark_as_sold = models.IntegerField(default=0) # 0-->not sold,1-->sold
def get_absolute_url(self):
return reverse('olx:edit_product', kwargs={'pk': self.pk})
class Meta:
ordering = ('-created',)
index_together = (('id','slug'),)# we want to query product by id and slug using together index to improve performance
def __str__(self):
return self.name
class Photo(models.Model):
reference_id = models.ForeignKey(Product, null=True,on_delete=models.CASCADE)
photo_type = models.CharField(max_length = 70, db_index = True)
file = models.FileField(upload_to='photos/',default='NoImage.jpg')
cover_photo_flag = models.CharField(default=0,max_length = 5, db_index = True)
uploaded_at = models.DateTimeField(auto_now_add=True)
uploaded_by_id = models.IntegerField(default=0)
status = models.IntegerField(default=0) # 0-->Active,1-->Inactive
class Meta:
ordering = ('-uploaded_at',)
class ProductLikeDislike(models.Model):
product_id = models.ForeignKey(Product,models.SET_DEFAULT,default=0)
product_liked_by_id = models.ForeignKey(User,models.SET_DEFAULT,default=0)
status = models.BooleanField(default=False)
And Please also show me how to write it in pure Django way if possible?
I am getting the same result. Why? Please help me to understand this.
Because .raw(..) [Django-doc] just takes a raw query and executes it. The model from which the raw is performed is irrelevant.
We can generate a query that looks like:
from django.db.models import Q
Product.objects.filter(
Q(photo__photo_flag__isnull=True) | Q(photo__photo_flag='yes'),
Q(likedislike__product_liked_by_id_id=2)
)
So here we accept all Products for which a related Photo object has a flag that is NULL (this also happens in case the JOIN does not yield any flags), or the photo_flag is 'yes'). Furthermore there should be a Likedislike object where the liked_by_id_id is 2.
Note that usually a ForeignKey [Django-doc] has no _id suffix, or id_ prefix. It is also a bit "odd" that you set a default=0 for this, especially since most databases only assign strictly positive values as primary keys, and it makes no sense to inherently prefer 0 over another object anyway.
Something like this:
user_i_care_about = User.objects.get(username='user2')
productlikedislike_set = models.Prefetch('productlikedislike_set',
ProductLikeDislike.objects.select_related('product_liked_by') \
.filter(product_liked_by=user_i_care_about) \
.order_by('id'))
photo_set = models.Prefetch('photo_set', Photo.objects.all()) # this is here incase you need to a select_related()
products = Product.objects.prefetch_related(photo_set, productlikedislike_set) \
.filter(models.Q(photo__cover_photo_flag='yes') | models.Q(photo__isnull=True)) \
.filter(productlikedislike__product_liked_by=user_i_care_about)
Then you can use:
for product in products:
for pic in product.photo_set.all():
print(x.file.name)
# every product here WILL be liked by the user
if your models look something like this:
class Product(models.Model):
# category = models.ForeignKey(Category, on_delete=models.CASCADE) # TODO: uncomment, didnt want to model this out
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=200, db_index=True)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2) # Not used FloatField to avoid rounding issues # this is correct, no need to explain this, anyonw that works with django, gets this.
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
contact = models.BigIntegerField(default=None,blank=True, null=True)
created_by = models.CharField(max_length=200, default=None, blank=True, null=True)
uploaded_by_id = models.IntegerField(default=0) # TODO: use ForeignKey(User) here!!!
status = models.IntegerField(default=0) # 0-->Active,1-->Inactive # TODO: learn to use `choices`
mark_as_sold = models.IntegerField(default=0) # 0-->not sold,1-->sold # TODO: there is something called `BooleanField` use it!
class Meta:
ordering = ('-created',)
index_together = (('id', 'slug'),) # we want to query product by id and slug using together index to improve performance
def get_absolute_url(self):
return reverse('olx:edit_product', kwargs={'pk': self.pk})
def __str__(self):
return self.name
class Photo(models.Model):
product = models.ForeignKey(Product, null=True,on_delete=models.CASCADE, db_column='reference_id')
photo_type = models.CharField(max_length=70, db_index=True)
file = models.FileField(upload_to='photos/', default='NoImage.jpg')
cover_photo_flag = models.CharField(default=0, max_length=5, db_index=True) # TODO: learn to use `choices`, and you use "yes" / "no" -- and the default is 0 -- FIX THIS!!
uploaded_at = models.DateTimeField(auto_now_add=True)
uploaded_by_id = models.IntegerField(default=0) # TODO: use ForeignKey(User) here!!!
status = models.IntegerField(default=0) # 0-->Active,1-->Inactive # TODO: learn to use `choices` -- perhaps just call this "is_active" and make it a bool
class Meta:
ordering = ('-uploaded_at',)
class ProductLikeDislike(models.Model):
product = models.ForeignKey(Product, models.SET_DEFAULT, default=0) # TODO: default=0?? this is pretty bad. models.ForeignKey(Product, models.SET_NULL, null=True) is much better
product_liked_by = models.ForeignKey(User, models.SET_DEFAULT, default=0, db_column='product_liked_by_id') # TODO: default=0?? this is pretty bad. models.ForeignKey(ForeignKey, models.SET_NULL, null=True) is much better
status = models.BooleanField(default=False) # TODO: rename, bad name. try something like "liked" / "disliked" OR go with IntegerField(choices=((0, 'Liked'), (1, 'Disliked')) if you have more than 2 values.
A full example WITH tests can be seen here: https://gist.github.com/kingbuzzman/05ed095d8f48c3904e217e56235af54a

Django save() always create a new object

I have this model in my app which is meant to auto-generate it's primary Key based on a method added in the save().
However, for each object, I will be expected to make updates of certain fields. Right now, anytime I make an update on the admin side (testing use cases) it instead creates a new record of the PK instead of updating the existing one. Any thoughts on how to remedy this?
class DeploymentTask(models.Model):
deployment_id = models.CharField(
'Deployment Task ID', primary_key=True, max_length=25, editable=False)
title = models.CharField(max_length=100)
current_status = FSMField('Current Status',
default=STATES[0], choices=STATES)
site_id = models.ForeignKey(
Site, related_name='+', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
refuel_record = models.ManyToManyField(RefuelRecord)
def __str__(self):
"""String for representing the Model object."""
return self.deployment_id
class Meta:
db_table = 'rm_deployment_task'
verbose_name_plural = 'Deployment Tasks'
def get_absolute_url(self):
return reverse('deployment_id-view', args=[str(self.deployment_id)])
def save(self):
today = datetime.datetime.now()
ticket_count = DeploymentTask.objects.filter(
created_at__year=today.year, created_at__month=today.month).count() + 1
new_task_id = 'DPT-' + str(str(datetime.date.today().year)) + str(
datetime.date.today().month).zfill(2) + str(
datetime.date.today().day).zfill(2) + '-' + str(ticket_count).zfill(6)
self.deployment_id = new_task_id
super(DeploymentTask, self).save()
enter image description here
You are always setting self.deployment_id to a new value in the save method.
Django tries to do UPDATE ... WHERE deployment_id = %, but there are no records with this id yet (at least if you are saving "old" object on different date or having different ticket_count)
If you want to update fields other than deployment_id, then simply do not set self.deployment_id if it's already set. If you want to update deployment_id then there is not straightforward way to do this, because it's used as primary key (but you can remember old pk and delete that object after you have created a new one during save)
Read more in the Django docs.
This is my updated code, which worked for me...
def make_id():
today = datetime.datetime.now()
ticket_count = DeploymentTask.objects.filter(
created_at__year=today.year, created_at__month=today.month).count() + 1
new_task_id = 'DPT-' + str(str(datetime.date.today().year)) + str(
datetime.date.today().month).zfill(2) + str(
datetime.date.today().day).zfill(2) + '-' + str(ticket_count).zfill(6)
return new_task_id
class DeploymentTask(models.Model):
deployment_id = models.CharField(
'Deployment Task ID', primary_key=True, max_length=25, editable=False, default=make_id)
title = models.CharField(max_length=100)
current_status = FSMField('Current Status',
default=STATES[0], choices=STATES)
site_id = models.ForeignKey(
Site, related_name='+', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
refuel_record = models.ManyToManyField(RefuelRecord)
def __str__(self):
"""String for representing the Model object."""
return self.deployment_id
class Meta:
db_table = 'rm_deployment_task'
verbose_name_plural = 'Deployment Tasks'
def get_absolute_url(self):
return reverse('deployment_id-view', args=[str(self.deployment_id)])

Django Admin Inline returning empty extra instances

First time posting, having a bit of a weird issue with Django's Admin TabularInline. Couldn't seem to find the problem in any searches.
When I add a value - in this case a Financial Quote - and save the entry, the page will refresh having added the instance and an additional 2 entries that have empty values in every field.
The same happens if I flag them for deletion from the admin page. It deletes all entries and then adds 3 more in the place of the previous ones.
The same happens with the Invoice model (which is a similar model) but not with the Purchase models which behaves as expected. This leads me to think i've done something odd when I've written the models.
Image attached to show the result.
Hopefully someone can see where i've gone wrong
Thanks!
models.py
class Quote(models.Model):
job = models.ForeignKey(Job, related_name="quotes", on_delete=models.CASCADE)
number = models.AutoField(primary_key=True)
currency = models.ForeignKey(Currency, blank=True, null=True)
amount = models.DecimalField(max_digits=20, decimal_places=2, default="0.00", verbose_name="Amount Invoiced")
created = models.DateTimeField(auto_now=False, auto_now_add=True)
created_by = models.ForeignKey(Profile, related_name='quoted', blank=True, null=True, on_delete=models.SET_NULL)
sent = models.BooleanField(default=False)
superceded = models.BooleanField(default=False)
tax = models.DecimalField(max_digits=20,decimal_places=2,default=20.00, verbose_name="Tax Rate")
def __unicode__(self):
return self.created.strftime("%B %d, %Y") + " | " + u'%s' % (self.currency) + str(self.amount)
def readable_date(self):
return self.created.strftime("%B %d, %Y")
class Invoice(models.Model):
job = models.ForeignKey(Job, related_name="invoices", blank=True, null=True, on_delete=models.SET_NULL)
number = models.AutoField(primary_key=True)
currency = models.ForeignKey(Currency, blank=True, null=True)
amount = models.DecimalField(max_digits=20, decimal_places=2, default="0.00", verbose_name="Amount Invoiced")
created = models.DateTimeField(auto_now=False, auto_now_add=True)
created_by = models.ForeignKey('profiles.Profile', related_name='invoiced', blank=True, null=True, on_delete=models.SET_NULL)
paid = models.BooleanField(default=False)
sent = models.BooleanField(default=False)
superceded = models.BooleanField(default=False)
tax = models.DecimalField(max_digits=20,decimal_places=2,default=20.00, verbose_name="Tax Rate")
def __unicode__(self):
return self.created.strftime("%B %d, %Y") + " | " + u'%s' % (self.currency) + str(self.amount)
def readable_date(self):
return self.created.strftime("%B %d, %Y")
def get_day(self):
return self.created.strftime("%d")
def get_month(self):
return self.created.strftime("%b")
admin.py
from finance.models import Purchase, Quote, Invoice
from django.contrib import admin
from .models import Job
class QuoteInline(admin.TabularInline):
model = Quote
class InvoiceInline(admin.TabularInline):
model = Invoice
class PurchaseInline(admin.TabularInline):
model = Purchase
class JobModelAdmin(admin.ModelAdmin):
list_display = [
'job_number',
'brand',
'job_name',
'client',
'account_manager',
'last_updated_by',
'updated',
'status',
]
list_display_links = ['job_name']
list_filter = ['client']
inlines = [
QuoteInline,
PurchaseInline,
InvoiceInline
]
Example of issue in admin page
In your inline classes set extra=0. I guess you have this problem because you have fields with default values and no any required fields in auto-created instances, so you accidentially save them, and django didn't raise any errors.