KagemaNjoroge / ultimate_pos

A simple Point of Sale System that just works!
https://malipo.tomorrow.co.ke
MIT License
7 stars 4 forks source link

Migrating from Django Views to Django REST Framework: Issues with Template Rendering and JSON Parsing #48

Open KagemaNjoroge opened 2 weeks ago

KagemaNjoroge commented 2 weeks ago

Body:

I'm currently migrating my Django project from plain Django views to Django REST framework views. Specifically, I'm working on expenses/views.py and templates/expenses/index.html. However, I'm facing several issues during this migration.

Issue Details:

1. Template Rendering Error:

I'm encountering an error when trying to render the index.html template for the GET request. The specific error is:

django.core.exceptions.ImproperlyConfigured: Returned a template response with no `template_name` attribute set on either the view or response

2. URL Reverse Error:

When trying to reverse the URL for the 'index' view, I get the following error:

django.urls.exceptions.NoReverseMatch: Reverse for 'index' not found. 'index' is not a valid view function or pattern name.

3. JSON Parsing Error:

When making a POST request, the server returns a 400 Bad Request error with the message:

"non_field_errors": ["Invalid data. Expected a dictionary, but got str."]

This is the data being sent:

"csrfmiddlewaretoken=Lni7iY5UwOzSbykUCbKu5ujBH8K7ZhudqsI2jxRGmnGQviyIpnqamWcicc22zQRZ&expense_name=Delivery%20to%20Kisii&amount=200&description=Delivery%20of%20a%20package%20to%20Keroka&category=2&id="

Code Details:

expenses/views.py:

from rest_framework.decorators import api_view, renderer_classes, parser_classes
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required
from .models import Expense, ExpenseCategory
from .serializers import ExpenseSerializer
from django.utils.decorators import method_decorator

@method_decorator(login_required(login_url="/users/login/"), name='dispatch')
@api_view(["GET", "POST", "PUT", "DELETE"])
@renderer_classes([JSONRenderer, TemplateHTMLRenderer])
@parser_classes([JSONParser])
def expenses_view(request):
    if request.method == "GET":
        recent_expenses = Expense.objects.order_by("-date_added")[:10]
        all_expense_categories = ExpenseCategory.objects.all()
        return Response(
            {"expenses": recent_expenses, "categories": all_expense_categories},
            template_name="expenses/index.html"
        )

    elif request.method == "POST":
        serializer = ExpenseSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(
                {
                    "status": "success",
                    "message": "Expense added",
                }
            )
        return Response(
            {
                "status": "error",
                "message": "Invalid data",
                "errors": serializer.errors,
            },
            status=400
        )

    elif request.method == "PUT":
        id = request.GET.get("id", None)
        if id:
            expense = get_object_or_404(Expense, id=id)
            serializer = ExpenseSerializer(expense, data=request.data, partial=True)
            if serializer.is_valid():
                serializer.save()
                return Response(
                    {
                        "status": "success",
                    }
                )
            return Response(
                {
                    "status": "error",
                    "message": "Invalid data",
                    "errors": serializer.errors,
                },
                status=400
            )
        return Response(
            {
                "error": "Invalid data",
                "status": "error",
            },
            status=400
        )

    elif request.method == "DELETE":
        id = request.GET.get("id", None)
        if id:
            try:
                expense = Expense.objects.get(id=id)
                expense.delete()
                return Response(
                    {
                        "status": "success",
                    }
                )
            except Expense.DoesNotExist:
                return Response(
                    {
                        "error": "Expense does not exist",
                        "status": "error",
                    },
                    status=404
                )
        return Response(
            {
                "error": "Invalid data",
                "status": "error",
            },
            status=400
        )

JavaScript Code:

$(document).ready(function () {
    $('#product_update').submit(function (e) {
        e.preventDefault();

        const url = "{% url 'products:products_update' product.id %}";
        const formData = new FormData($('#product_update')[0]);
        const jsonObject = {};

        formData.forEach((value, key) => {
            jsonObject[key] = value;
        });

        $.ajax({
            type: 'POST',
            url: url,
            contentType: 'application/json',
            headers: {
                "X-CSRFToken": $('input[name="csrfmiddlewaretoken"]').val()
            },
            data: JSON.stringify(jsonObject),

            success: function (response) {
                if (response['status'] === "success") {
                    $('.update_msg').html(
                        '<div class="alert alert-success alert-dismissible fade show" role="alert"><strong>Success!</strong> Product updated successfully<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>'
                    );
                } else {
                    $('.update_msg').html(
                        '<div class="alert alert-danger alert-dismissible fade show" role="alert"><strong>Error!</strong> Product not updated<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>'
                    );
                }
            },
            error: function () {
                $('.update_msg').html(
                    '<div class="alert alert-danger alert-dismissible fade show" role="alert"><strong>Error!</strong> Product not updated<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>'
                );
            }
        }).done(function () {
            // remove spinner from the button
            $('#update_btn').html('Update product');
        });
    });
});

Additional Information:

Any help or guidance to resolve these issues would be greatly appreciated. Thank you!

darthpedroo commented 2 weeks ago

Hey! Correct me if I am wrong, but I think that in regards to the

  1. JSON Parsing Error:

the problem is that you are sending a string instead of the proper JSON Response. If your model is

class Expense(models.Model): 

expense_name = models.CharField( max_length=100, blank=False, null=False, unique=True ) 

expense_description = models.CharField(max_length=300, blank=True, null=True) 

category = models.ForeignKey(ExpenseCategory,   on_delete=models.CASCADE)

amount = models.FloatField(default=0.00)

date_added = models.DateTimeField(auto_now_add=True) 

date_modified = models.DateTimeField(auto_now=True)

then you should pass the data in the form of a JSON, and each key should correspond to a field of the model.

For example, an appropriate input could be:

{"expense_description": "your description",
 "category": "your FK",
 "amount": "your amount",
 "date_added": "your_date",
 "date_modified": "your_date"}

I don't really know which kind of data you are passing to the api from javascript, but if it is something like this: django.urls.exceptions.NoReverseMatch: Reverse for 'index' not found. 'index' is not a valid view function or pattern name. as you stated before it will throw an error.

Hope my answer was what you were looking for and tell me if you need help with something else :)

edit: i embeded the code correctly

KagemaNjoroge commented 2 weeks ago

Thanks very much for the help. Could you help me migrate the views in expenses app to standalone rest_framework views. @darthpedroo