Django generic create view . Can we add conditions to save data from the form?

Welcome to Programming Tutorial official website. Today - we are going to cover how to solve / find the solution of this error Django generic create view . Can we add conditions to save data from the form? on this date .

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")