post action drops part of the url in django app

Question:

I’m building my first ecommerce app, while attempting to gain more skills in Django. I have a form problem, where I am either adding a product, or editing one, using the same Template. My problem is where the action call drops part of the url when submitting POST back to the server ( right now, it’s just the python manage.py runserver).

When I go to edit, I see the url: http://127.0.0.1:8000/mystore/edit-product/4
When I edit the product and click submit, I get the Request URL: http://127.0.0.1:8000/mystore/edit-product/ page not found error.
This error prevents me from returning to the view to determine anything else. please note, I am missing the last part of the url (4), which is the crux of my issue.

It looks like I’m missing something. This is what I have

userprofile/url.py

from django.urls import path
from django.contrib.auth import views as auth_views

from . import views

urlpatterns = [
    path('signup/', views.signup, name='signup'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('login/', auth_views.LoginView.as_view(template_name='userprofile/login.html'), name='login'),
    path('myaccount/', views.myaccount, name='myaccount'),
    path('mystore/', views.my_store, name='my_store'),
    path('mystore/add-product/', views.add_product, name='add-product'),
    path('mystore/edit-product/<int:pk>', views.edit_product, name='edit-product'),
    path('vendors/<int:pk>/', views.vendor_detail, name='vendor_detail')
]

store/urls.py

from django.urls import path
from . import views

urlpatterns =[
    path('search/', views.search, name='search'),
    path('<slug:slug>', views.category_detail, name='category_detail'),
    path('<slug:category_slug>/<slug:slug>', views.product_detail, name='product_detail')
]

core/urls.py <– the base urls

from django.urls import path,include
from .views import frontpage, about

urlpatterns =[
    path('', include('userprofile.urls')),
    path('', frontpage, name='frontpage'),
    path('about', about, name='about'),
    path('', include('store.urls'))
]

Views

@login_required
def add_product(request):
    if request.method == 'POST':
        form = ProductForm(request.POST, request.FILES)
        if form.is_valid():
            title = request.POST.get('title')
            slug = slugify(title)
            product = form.save(commit=False)
            product.user = request.user
            product.slug = slug
            product.save()
            return redirect('my_store')
    else:
        form = ProductForm()
    return render(request, 'userprofile/add-product.html', {
        'title': 'Add Product',
        'form':form
    })

@login_required
def edit_product(request, pk):
    product = Product.objects.filter(user=request.user).get(pk=pk)

    if request.method == 'POST':
        form = ProductForm(request.POST, request.FILES, instance=product)

        if form.is_valid():
            form.save()
            return redirect('my_store')
    else:
        form = ProductForm(instance=product)
    return render(request, 'userprofile/add-product.html', {
        'title': 'Edit Product',
        'form': form
    })

add-product.html (Note: template is used for both add and edit. the variable title distinguishes from the two.)

{% extends 'core/base.html' %}

{% block title %}My Store{% endblock %}

{% block content %}
    <h1 class="text-2xl">My Store</h1>

    <h2 class="mt-6 text-xl ">{{ title }}</h2>

    <form method="post" action="." enctype="multipart/form-data">
        {% csrf_token %}
        {{ form.as_p }}
        <button class="mt-6 py-4 px-8 bg-teal-500 text-white hover:bg-teal-800">Save</button>
    </form>

{% endblock %}

form.py

from django import forms
from .models import Product

class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ('category', 'title', 'description', 'price', 'images', )

logs

[26/Dec/2022 20:27:32] "GET /mystore/edit-product/4 HTTP/1.1" 200 2690
Not Found: /mystore/edit-product/
[26/Dec/2022 20:27:47] "POST /mystore/edit-product/ HTTP/1.1" 404 5121

The not found indicates the response from pressing the "submit" button.

Thank you.

Asked By: arcee123

||

Answers:

I think that specifying an apt url in the form action of your ‘add-product.html’ would direct you to the desired page.
<form method="post" action="{% url 'appName: pathName'%}">

The path name would probably be anyone of the desired url specified in urls.py

Answered By: Ipvikukiepki-KQS

Update:

For some reason, the relative path indicated in your form action, e.g. action="." does not refer to your current page, but to it’s parent "directory". Perhaps the "." means "current directory" and not "current URL". However, since the default action is performed at the current URL, you can simply omit the action property and it will submit at current URL.

Previous:

Looks like you are redirecting to a different page when the form is valid:

if form.is_valid():
  form.save()
  return redirect('my_store')

Sounds like you may want this branch to end with logic that renders your updated form rather than redirecting you somewhere. In this case, you are being redirected to a URL with no corresponding view, which is why you keep getting a 404 error message.

Answered By: DragonBobZ

Form, view, template, urls in general look fine.

I guess it’s a result of a combination: relative url action="." and page URL without trailing slash – path('mystore/edit-product/<int:pk>').

You can fix the issue these ways:

  1. put full url into form definition e.g. action="{% url 'edit-product' product.pk %}"
  2. fix url pattern, add trailing slash path('mystore/edit-product/<int:pk>/') thus the page will be opened as /mystore/edit-product/4/

I guess since you render the same template for add and edit option 1 is a no go, so fix url pattern. For better support from Django enable option APPEND_SLASH in your settings.py

Here is a test:

If you open this question’s page with trailing slash in the address https://stackoverflow.com/questions/74922497/post-action-drops-part-of-the-url-in-django-app/ code below would generate link with full page adress

<a href=".">relative link</a>

But if you omit trailing slash in the same page address (SO does not redirect and can show same page under both urls) then the same link "." will address https://stackoverflow.com/questions/74922497/ Which is exactly your case.

Note, you have such url patterns without trailing slash in store/urls.py as well.

ps while I was writing @DragonBobZ mentioned the same reason.

Answered By: Ivan Starostin