Django generic create view . Can we add conditions to save data from the form?
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'
Answer
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")