When i submit my form My Comment form dosen't save in database

I create a 5 star rating system using django with javascript and i want to user comment like this:template

i want to click on the stars and then return the value that is an integer

this is my models:

class Review(models.Model):

course = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='reviews')

first_name = models.CharField(max_length=50)

last_name = models.CharField(max_length=50)

rating = models.IntegerField(null=True, validators=[MinValueValidator(1), MaxValueValidator(5)])

comment = models.TextField()

created = models.DateField(auto_now_add=True)

active = models.BooleanField(default=False)

def __str__(self):

    return f'{self.first_name} {self.last_name}

my views:

@csrf_exempt
def productDetailView(request, id, slug):
    product = get_object_or_404(Product, id=id, slug=slug, available=True)
    new_comment = None
    
    if request.method == 'POST':
        form = ReviewForm(request.POST)
        if form.is_valid():
            new_comment = form.save(commit=False)
            new_comment.course = product
            new_comment.rating = request.POST['rating']
            new_comment.save()
    else:
        form = ReviewForm()
    
    return render(request, 'shop/product_detail.html', {'product': product, 'form': form})

js function:

$(document).ready(function(){
        $('.rate .rate-item').on('click', function(){
            var value = $(this).data('value');
            $.ajax({
                url: '{{ product.get_absolute_url }}',
                type: 'POST',
                data: {'rating': value},
                success: function(response){
                    alert('Rating saved successfully!');
                }
            });
        });
    });

and my template:

<form method="post">
<div class="form-singel">
{{ form.first_name|attr:" placeholder:Fast name" }}
</div>
<div class="form-singel">
{{ form.first_name|attr:" placeholder:Last Name"}}
</div>
<div class="rate-label">Your Rating:</div>
<div class="rate">
<div data-value="1" class="rate-item"><i class="fa fa-star" aria-hidden="true"></i></div>
<div data-value="2" class="rate-item"><i class="fa fa-star" aria-hidden="true"></i></div>
<div data-value="3" class="rate-item"><i class="fa fa-star" aria-hidden="true"></i></div>
<div data-value="4" class="rate-item"><i class="fa fa-star" aria-hidden="true"></i></div>
<div data-value="5" class="rate-item"><i class="fa fa-star" aria-hidden="true"></i></div>
</form>
<div class="form-singel">
{{ form.first_name|attr:" placeholder:Comment" }}                                                                                               
</div>
<div class="form-singel">
<button type="submit" class="main-btn">Post Comment</button>
</div>

How do i fix it?

You do not need AJAX to send rating value separately. Instead you can use JavaScript to update the form field rating value (while working with star elements feature), and submit it once it is ready:

forms.py

class ReviewForm(forms.ModelForm):
    class Meta:
        model = Review
        fields = ['first_name', 'last_name', 'comment', 'rating', 'course']
        labels = {
            'rating': '',
            'course': '',
        }
        widgets = {
            'rating': forms.HiddenInput(),
            'course': forms.HiddenInput(),
            'first_name': forms.TextInput(attrs={'placeholder': 'First Name'}),
            'last_name': forms.TextInput(attrs={'placeholder': 'Last Name'}),
            'comment': forms.Textarea(attrs={'placeholder': 'Comment'}),
        }

views.py

def product_detail(request, id, slug=None):
    product = get_object_or_404(Product, id=id, available=True)
    
    if request.method == 'POST':
        form = ReviewForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, "Comment saved successfully")
            # Probably want to redirect somewhere else
            return redirect(reverse('core:product_detail', kwargs={'id': product.id}))

    else:
        form = ReviewForm(initial={'course': product})
    
    return render(request, 'shop/product_detail.html', {'product': product, 'form': form})

A few notes:

  1. Good practice is functions in snake_case and classes in CamelCase.
  2. You do no need two fields to retrieve the object. Either go with id or slug. Since you did not share all models I am not sure if slug is also unique so I just used id as usual.
  3. Ensure csrftoken presence in your requests, specially when modifying the database, it is a security matter. Of course it is also possible to use it with AJAX.

shop/product_detail.html:

<form method="POST" action="">
    {% csrf_token %}
    {% for field in form %}
        <div class="fieldWrapper">
            {{ field.errors }}
            <label for="{{ field_name.label_tag }}">{{ field.label_tag }}</label><br>
            {{ field }}
        </div>
    {% endfor %}
    <div class="rate-label">Your Rating:</div>
    <div id="rate" style="display: flex; flex-flow: row nowrap;">
        <div data-value="1" class="rate-item"><i class="far fa-star" aria-hidden="true"></i></div>
        <div data-value="2" class="rate-item"><i class="far fa-star" aria-hidden="true"></i></div>
        <div data-value="3" class="rate-item"><i class="far fa-star" aria-hidden="true"></i></div>
        <div data-value="4" class="rate-item"><i class="far fa-star" aria-hidden="true"></i></div>
        <div data-value="5" class="rate-item"><i class="far fa-star" aria-hidden="true"></i></div>
    </div>
    <br>
    <input type="submit" value="Post Comment">
</form>

<script>
    const filledStarClass = 'fas fa-star';
    const emptyStarClass = 'far fa-star';

    $('.rate-item').on('click', function(){
        var value = $(this).data('value');
        updateStars(value);
        $('#id_rating').val(value)
    });

    function updateStars(value) {
        var childDivs = $('#rate').children();
        for( i=0; i< value; i++ ) {
            var star = childDivs[i].children[0];
            star.className = filledStarClass;
        }

        if (value < 5) {
            for( i=value; i< childDivs.length; i++ ) {
                var star = childDivs[i].children[0];
                star.className = emptyStarClass;
            }
        }
    }
</script>

On template, the main point is at $('#id_rating').val(value) where we update the form value based on the star item data-value.

Back to Top