Is there's a "cross-saving"-possibility between to models inside my django-application?
For example:
models.py
class Person(models.Model):
first_name = models.CharField()
last_name = models.CharfFeld()
age = models.IntegerField()
value_person= models.IntegerField()
date_created = models.DateField
class Account():
name = models.CharField()
description = models.Textarea()
person = models.ForeignKey(Person, null=False, blank=False, on_delete=models.CASCADE)
value_a = models.IntegerField()
value_b = models.IntegerField()
date_created = models.DateField
forms.py:
class UpdateAccountForm(ModelForm):
class Meta:
model = Account
exclude = [
'name',
'date_created',
]
views.py:
[...]
account_db = Account.objects.get(id=pk)
form = UpdateAccountForm(instance=Account)
if request.method == 'POST':
form = CreateAccountForm(request.POST, instance=Account)
if form.is_valid():
form.save()
*** Do something here
return("Somehwere")
[...]
Now I'm asking myself, if it's possible - while updating the "Account" - to update the "Person" and - let's say for example - add one of the two "values_..." into the "value_person"? The connection should be there via the "ForeignKey", right? I think I should also mention, that I'm talking about my default database!
I'm aware that this might be a bad example, but it's just so that I can understand the system behind this - hopefully existing - function!
Of course, you can do this with the code below. However, as far as I know, there is no function that does this. foreign key relations
account_instance = Account.object.get(id=1)
person = account_instance.person
person.value_person += 1
person.save()
For example, if you want it only in view, you can do as in the code below
[...]
account_db = Account.objects.get(id=pk)
form = UpdateAccountForm(instance=Account)
if request.method == 'POST':
form = CreateAccountForm(request.POST, instance=Account)
if form.is_valid():
form.save()
person = account_db.person
person.value_person += 1
person.save()
return("Somehwere")
[...]
Or if you want this logic to be executed every time when model is saved, you can do with the overriding the save method of the Account class. overriding model save
class Account():
name = models.CharField()
description = models.Textarea()
person = models.ForeignKey(Person, null=False, blank=False, on_delete=models.CASCADE)
value_a = models.IntegerField()
value_b = models.IntegerField()
date_created = models.DateField
def save(self, *args, **kwargs):
self.person.value_person += 1
self.person.save()
super().save(*args, **kwargs) # don't forget to call super method
Also, I don't recommend it but you can use the post save signal. As a note, it is difficult to debug signals.django doc
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Account)
def increase_person_values(sender, instance, **kwargs):
instance.person.value_person += 1
instance.person.save()
Related
I have been spinning my wheels on this issue for a day or two. I have django web application that has 3 models
users/models.py:
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
# Create your models here.
class ExtendedUser(models.Model):
user=models.OneToOneField(User,null=True, on_delete=models.CASCADE)
def full_name(self):
return (self.user.get_full_name())
def __str__(self):
return self.user.get_full_name()
#receiver(post_save, sender=User)
def create_or_update_user_extendeduser(sender, instance, created, **kwargs):
if created:
ExtendedUser.objects.create(user=instance)
instance.extendeduser.save()
playground/models.py:
class Customer(models.Model):
def __str__(self):
return self.Customer_Name
Customer_Name = models.CharField(max_length=100)
SFDC_Customer_Record_Number = models.IntegerField(default='')
Zone = models.CharField(max_length=50, default='')
Government = models.BooleanField(default=False)
date_posted = models.DateTimeField(default=timezone.now)
customerauthor = models.ForeignKey(ExtendedUser, on_delete=models.DO_NOTHING,default=ExtendedUser)
def get_absolute_url(self):
return reverse('playground-home')
class Vue_Quote(models.Model):
def __str__(self):
return self.Quote_Name
Quote_Name = models.CharField(max_length=100)
SFDC_Golden_Opp_ID = models.IntegerField()
Vue_System_Count = models.IntegerField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(ExtendedUser, on_delete=models.DO_NOTHING,default=ExtendedUser,blank=True,null=True)
Quote_Type = models.CharField(max_length=100)
Customer = models.ForeignKey(Customer, on_delete=models.DO_NOTHING, default='')
def get_absolute_url(self):
return reverse('quote-detail',kwargs={'pk': self.pk})
I am using the 3rd party application django-extra-views to create a single form which allows a user to create a customer and quote at the same time. Views.py:
class QuoteInline(InlineFormSetFactory):
model = Vue_Quote
fields = ['Quote_Name','SFDC_Golden_Opp_ID','Vue_System_Count','Quote_Type',]
factory_kwargs = {'extra':1}
class CreateQuoteInlinesView(CreateWithInlinesView):
model = Customer
inlines = [QuoteInline]
fields = ['Customer_Name','SFDC_Customer_Record_Number','Zone','Government']
template_name= 'quote_and_customer.html'
def forms_valid(self, form, inlines):
form.instance.customerauthor = ExtendedUser.objects.get(user=self.request.user)
return super().forms_valid(form,inlines)
All of this is working great except for that I am not able to save the author for the Vue_Quote model...I always get "None":
Image of Vue_Quote.author = None from my form
I have tried a wide range of solutions but cannot seem to solve this and I am finding very little documentation on django-extra-views to support my finding a solution.
Any assistance is greatly appreciated!
Welp, I've looked at the source code and tried to figure it out.. It's really abstract and a lot of inheritance
This would be my best guess:
def forms_valid(self, form, inlines):
user = ExtendedUser.objects.get(user=self.request.user)
form.instance.customerauthor = user
for i in inlines:
i.instance.author = user
i.save()
return super().forms_valid(form,inlines)
Or if you wanted to also define the Customer field in this forms_valid you could do it after the super call
You could try something like this:
def forms_valid(self, form, inlines):
user = ExtendedUser.objects.get(user=self.request.user)
form.instance.customerauthor = user
customerObj = super().forms_valid(form,inlines)
for i in inlines:
i.instance.author = user
i.instance.Customer = customerObj
i.save()
return customerObj
I'm using django to create a signup platform where students can signup to weekly classes.
Each class is a django model called ClassCards which has a ManytoMany relation to User model called signed_up_student that represents all the users signed up for that class as seen below
class ClassCards(models.Model):
content = models.CharField(max_length=100, default = '')
date = models.DateField(blank=True, null = True)
time = models.TimeField(blank=True,null=True)
signed_up_students = models.ManyToManyField(User,blank=True)
full = models.BooleanField(default = False)
max_students = models.IntegerField(default=4)
teacher = models.CharField(max_length=100, default='Adi')
I would like to add a subscription option that will automatically sign up subscribed students to this weeks class. Here is my Subscription model:
class Subscriptions(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null =True)
day = models.CharField(max_length=100, choices=day_choices, null=True)
time = models.TimeField(blank=True, null=True)
num_of_times_used = models.IntegerField(default=0)
cap = models.IntegerField(default=52)
active = models.BooleanField(default= True)
expirey_date = models.DateField()
date_created = models.DateField(default = timezone.now)
To accomplish this I have created a post_save signal:
#receiver(post_save,sender=ClassCards)
def apply_subsciptions(sender,instance,created, **kwargs):
if created:
subs = Subscriptions.objects.filter(day=instance.date.strftime("%A"),
time=instance.time)
for s in subs:
instance.signed_up_students.add(s.user)
print(instance.signed_up_students.get())
The code runs properly when a ClassCards is saved without throwing any errors and the print statement prints the relevant User However when I look on the admin page, I see that there are no users in the signed_up_students field.
I Would like to understand why this isn't working as desired which should adding that user to the ManytoMany field and what is the best practice for automatically updated a ManytoMany fields.
a little modification to the class ClassCards
class ClassCards(models.Model):
signed_up_students = models.ManyToManyField(User, symmetrical=False, related_name="student_cards_students_has", blank=True)
def signed_students_list(self):
return self.signed_up_students.all()
def cards_asigned_to_student_list(self):
return self.student_cards_students_has.all()
def assign_student(self, user):
self.signed_up_students.add(user)
def unsign_user(self, user):
self.signed_up_students.remove(user)
now in the signals
#receiver(post_save,sender=ClassCards)
def apply_subsciptions(sender,instance,created, **kwargs):
if created:
subs = Subscriptions.objects.filter(day=instance.date.strftime("%A"),time=instance.time)
for s in subs:
instance.assign_student(s.user)
instance.save()
print(instance.signed_students_list())
print(instance.cards_asigned_to_student_list())
This is what my code looks like and I want to add some condition to the Borrower create view like if the stock method of book returns 0 then don't list that book in field while creating a new borrower or if it isn't possible at least throw some error while adding borrower to that book.
models.py:
class Book(models.Model):
id = models.UUIDField(primary_key=True, unique=True,
default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
summary = models.TextField(
max_length=1000, help_text="Enter a brief description of the book")
isbn = models.CharField('ISBN', max_length=13,
help_text='13 Character https://www.isbn-international.org/content/what-isbn')
genre = models.ManyToManyField(
Genre, help_text="Select a genre for this book")
language = models.ForeignKey(
'Language', on_delete=models.SET_NULL, null=True)
total_copies = models.IntegerField()
pic = models.ImageField(blank=True, null=True, upload_to='books')
def stock(self):
total_copies = self.total_copies
available_copies = total_copies - \
Borrower.objects.filter(book=self).count()
if available_copies > 0:
return available_copies
else:
return 0
def __str__(self):
return self.title
class Borrower(models.Model):
id = models.UUIDField(primary_key=True, unique=True,
default=uuid.uuid4, editable=False)
student = models.ForeignKey('Account', on_delete=models.CASCADE)
book = models.ForeignKey('Book', on_delete=models.CASCADE)
issue_date = models.DateField(
null=True, blank=True, help_text='YYYY-MM-DD', default=date.today)
return_date = models.DateField(
null=True, blank=True, help_text='YYYY-MM-DD')
def __str__(self):
return self.student.name.title()+" borrowed "+self.book.title.title()
def fine(self):
today = date.today()
fine = 0
if self.return_date <= today:
fine += 5 * (today - self.return_date).days
return fine
views.py:
class BorrowerView(LoginRequiredMixin, ListView):
model=Borrower
context_object_name='borrowers'
template_name = 'library/borrower_list.html'
def get_context_data(self, **kwargs):
context=super().get_context_data(**kwargs)
if self.request.user.is_admin or self.request.user.is_superuser:
context['borrowers']=context['borrowers']
else:
context['borrowers']=context['borrowers'].filter(student = self.request.user.id)
return context
class BorrowerCreate(LoginRequiredMixin, UserAccessMixin, CreateView):
model=Borrower
permission_required= 'borrowers.add_borrowers'
fields='__all__'
success_url=reverse_lazy('library:borrower-list')
def form_valid(self, form):
form.instance.user=self.request.user
return super(BorrowerCreate, self).form_valid(form)
class BorrowerDetail(LoginRequiredMixin, DetailView):
model=Borrower()
context_object_name='borrower'
template_name='library/borrower.html'
class Book(models.Model):
id = models.UUIDField(primary_key=True, unique=True,
default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
summary = models.TextField(
max_length=1000, help_text="Enter a brief description of the book")
isbn = models.CharField('ISBN', max_length=13,
help_text='13 Character https://www.isbn-international.org/content/what-isbn')
genre = models.ManyToManyField(
Genre, help_text="Select a genre for this book")
language = models.ForeignKey(
'Language', on_delete=models.SET_NULL, null=True)
total_copies = models.IntegerField()
pic = models.ImageField(blank=True, null=True, upload_to='books')
#new, use this to keep track of available books
available_copies = models.IntegerField()
def __str__(self):
return self.title
When any borrower borrows a copy of the book, you will subtract it from the total copies.
class BorrowerCreate(LoginRequiredMixin, UserAccessMixin, CreateView):
model=Borrower
permission_required= 'borrowers.add_borrowers'
fields='__all__'
success_url=reverse_lazy('library:borrower-list')
#remember to get the object using slug or 404
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
book = Book.objects.get(id=instance.book.id)
#get the book id from the form and check if the book is still available, then subtract.
if book.available_copies > 0:
book.available_copies -= 1
book.save()
instance.save()
message.success(self.request, _("successful")
message.error(self.request, _("Book not in stock")
return super(BorrowerCreate, self).form_valid(form)
If user return the book and click returned. you can perform a similar action by adding to available copies.
This is not the solution, you can write a fat model with methods that takes care of both borrowing and return. Like this
def borrow(self):
self.available_copies -= 1
def returned(self):
self.available_copies += 1
You can call these two methods in different views or define a signal that call them using pre_save
Ist:
Instead of defining a new method which you called stock, why not add stock as a field instead of making a database query.
2nd:
calling the class inside the same class is not the best way to make queries inside the class.
3rd:
To add a condition while adding an object, you need to override the save method of the object like this.
def save(self, *args, **kwargs):
# do_something() here.....
# then
return super().save(*args, **kwargs)
The above code will enable you to perform any action before saving the object.
Another way you can do this is inside the form_valid function like this.
def form_valid(self, form):
instance = form.save(commit=False)
# commit = False will make sure that the form is not saved
# then you can now query the database and check conditions like
if Borrower.object.all().count() > 0:
instance.save()
messages.success(self.request, _("saved successfully")
else:
messages.error(self.request, _("Error")
return redirect("URL")
My following question is about how I can develop a function that I can compare a POST request data (ModelForm) and existing data of model in queryset.
This is mi models.py:
class Employee(models.Model):
dni = models.CharField(max_length=9, null=False, blank=False, default="12345678R")
name = models.CharField(max_length=7)
surname = models.CharField(max_length=8)
email = models.CharField(max_length=20)
telefone_number = models.IntegerField()
user_nick = models.CharField(max_length=10, null=False, blank=False, default="user")
password = models.CharField(max_length=20, null=False, blank=False, default="password")
ticket = models.ManyToManyField(Ticket)
forms.py (EmployerLoginForm only to user_nick and password):
class EmployerForm(forms.ModelForm):
class Meta:
model = Employee
fields = "__all__"
class EmployerLoginForm(forms.ModelForm):
class Meta:
model = Employee
exclude = ['dni', 'name', 'surname', 'email', 'telefone_number', 'ticket']
In this case, to develop login function I am using the EmployerLoginForm in views.py:
_logger = nexus_services_logs.Logging(statics.NEXUS_VIEWS_LOGGING_NAME)
_views_manager_service = nexus_services_views_manager.ViewsManagerService()
_auth = nexus_services_auth.Authentication()
class IndexView(View):
def post(self, request, *args, **kwargs):
form = EmployerLoginForm(request.POST)
if(_auth.check_model_employer_authentication(form, _logger, _views_manager_service)):
if(_views_manager_service.validate_form(form, _logger)):
_views_manager_service.save_form(form, _logger)
return redirect('employerPortal')
else:
return redirect('index')
check_model_employer_authentication(form, _logger, _views_manager_service) is the function where I want compare form data and queryset. I find the problem when I cannot compare the objects using for loop (in auth.py):
class Authentication():
def __init__(self):
self.employer_exist = False
def check_model_employer_authentication(self, model, logger, views_manager_service):
queryset_all_employers = Employee.objects.order_by("id")
context_exployers = views_manager_service.build_context_queryset_employers(queryset_all_employers)
for employer in context_exployers["employers"]:
if(employer.user_nick == model.user_nick and employer.password == model.password):
self.employer_exist = True
logger.info_log("Exist nick with similar password")
return True
else:
logger.error_log("Nick or password not exist or coincidence with object in db")
return False
I have tried using a context but not works.
Here i have two models.In these models i want to make the value of amount_to_pay dynamic in Ledger model.For Example i have two different forms for these two models and while saving expense form if the user select the payment_option which comes from ledger model and gives some value for the amount_to_pay field then if only ledger.id and expense.payment_option_id are same then the value of amount_to_pay in ledger model should be replaced with that value.how can i do it ?
models.py
class Expense(models.Model):
pay_from = models.CharField(max_length=200)
payment_option = models.ForeignKey('Ledger', on_delete=models.CASCADE)
amount_to_pay = models.IntegerField(default=0)
expense_date = models.DateField(default=datetime.date.today)
expense_type = models.ForeignKey(ExpenseType, on_delete=models.CASCADE)
note = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
slug = AutoSlugField(unique_with='id', populate_from='expense_type')
def get_amount_to_pay(self):
return self.amount_to_pay
class Ledger(models.Model):
name = models.CharField(max_length=200)
account_number = models.CharField(max_length=250, unique=True)
account_type = models.CharField(max_length=200)
opening_balance = models.IntegerField(default=0)
amount_to_pay = models.IntegerField(default=0, blank=True, null=True)
current_balance = models.IntegerField(default=0, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
slug = AutoSlugField(unique_with='id', populate_from='name')
def save(self, *args, **kwargs):
self.amount_to_pay = Expense.get_amount_to_pay(self)
# here how can i save the amount_to_pay from expense form if the ledger.id and expense.payment_option.id matches??
#i got stuck here.
self.current_balance = self.opening_balance - self.amount_to_pay
super(Ledger, self).save(*args, **kwargs)
Solution One:
I think instead of changing in Ledger model, you should change in Expense model, like this:
class Expense(models.Model):
...
def save(self, *args, **kwargs):
self.payment_option.amount_to_pay = self.payment_option.amount_to_pay + self.amount_to_pay
self.payment_option.save()
super(Expense, self).save(*args, **kwargs)
Solution Two:
But to be honest, Solution One does not seem good to me. Reason is that you are saving same data in 2 places(in both expense and ledger). Instead, it should be once, then the amount_to_pay value in Ledger should be calculated dynamically. Like this:
from django.db.models import Sum
class Ledger(...):
#property
def amount_to_pay(self):
# I am using a property method to show the amount_to_pay value.
# FYI: in this way, you need to remove amount_to_pay field from Ledger model
return self.opening_balance - self.expense_set.all().aggregate(a_sum=Sum('amount_to_pay')).get('a_sum', 0)
In that way, with each ledger, the value amount_to_pay will be dynamically calculated at runtime. For example:
for l in Ledger.objects.all():
l.amount_to_pay
Solution Three:
If you are wary of making DB hits with each l.amount_to_pay(as it calculates amount_to_pay from DB dynamically) from previous solution, then you can always annotate the value. Like this:
For this solution, you need to change your Expense model and add a related_name:
class Expense(models.Model):
pay_from = models.CharField(max_length=200)
payment_option = models.ForeignKey('Ledger', on_delete=models.CASCADE, related_name='expenses')
Then use that related_name in query like this(FYI: You can't keep def amount_to_pay(...) method in Ledger model for the following usage example):
from django.db.models import Sum, F, ExpressionWrapper, IntegerField
ledgers = Ledger.objects.all().annotate(expense_sum=Sum('expenses__amount_to_pay')).annotate(amount_to_pay=ExpressionWrapper(F('opening_balance') - F('expense_sum'), output_field=IntegerField()))
# usage one
for l in ledgers:
l.amount_to_pay
# usage two
ledgers.values('amount_to_pay')
Best thing is you override the call in save method any way you have foreginkey.
def save(self, *args, **kwargs):
#self.date_created = timezone.now()
# YOUR LOGIC HERE
super(YOUR_OVERRIDING_MODEL , self).save(*args, **kwargs
check if Ledger have child and then update it:
class Expense(models.Model):
def save(self, *args, **kwargs):
self.payment_option.amount_to_pay = self.payment_option.amount_to_pay + self.amount_to_pay
self.payment_option.save()
super(Expense, self).save(*args, **kwargs)
Override save method and update some field dynamically in django
For my case i wanted to update done column in answer model dynamically when i comment on an answer it should return true instead of False. So in my example i am checking for whether that column done which is boolean is False and if it's False,i change it's status to True using the answer object from Comment and then save the answer object, see more about update vs insert fields https://docs.djangoproject.com/en/3.2/ref/models/instances/
class Answer(models.Model):
user = models.ForeignKey("User", on_delete=models.CASCADE, blank=True, null=True)
username = models.CharField(max_length=250)
codeAnswer = models.CharField(max_length=250)
girAnswer = models.CharField(max_length=250)
correct = models.BooleanField(default=False)
firebaseToken = models.CharField(max_length=250)
done = models.BooleanField(default=False)
created_at = models.DateTimeField()
updated_at = models.DateTimeField(auto_now=True)
def __unicode__(self):
return "{0}: {1}".format(self.username, self.value)
def __str__(self):
return "%s" % (self.codeAnswer)
# So here i override the save method in the Comment model to do what i want as below.
class Comment(models.Model):
answer = models.ForeignKey("Answer", on_delete=models.CASCADE, blank=False , related_name="comments")
writerName = models.CharField(max_length=250)
content = models.CharField(max_length=250)
deleted = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return "%s" % (self.writerName)
def save(self, *args, **kwargs):
if self.answer.done is False:
self.answer.done = True
self.answer.save()
super(Comment, self).save(*args, **kwargs)
Hope it answers the question above thanks