0

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.

arcee123
  • 101
  • 9
  • 41
  • 118

3 Answers3

3

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.

DragonBobZ
  • 2,194
  • 18
  • 31
  • yes. In this case, the template "add_product.hmtl" is being used as both the add and edit forms. the two views point to the same template. the `action="."` line in the html form is supposed to indicate to go back to the "current" view. however, my_store is the store front for that user. If I make a successful edit, the system is supposed to re-route me back to that page. – arcee123 Dec 26 '22 at 19:59
  • The `action="."` simply means that the POST action is executed at the current URL. In other words, it sends your form content to the server at the current URL and that server decides what to do after that. So that `action` has no control where you end up, after the request is processed, the server ultimately decides. If you want to redirect to your detail view, you need to do something like: `return redirect('my_store:product_detail', {"slug": , "category_slug": })` – DragonBobZ Dec 26 '22 at 20:07
  • Alternatively, instead of redirecting, you can just return the rendered form template. Because you aren't redirecting, your URL will not change. – DragonBobZ Dec 26 '22 at 20:17
  • my problem is that the error is before that. it's trying to find `http://127.0.0.1:8000/mystore/edit-product/` when I hit the save button. it should be looking for `http://127.0.0.1:8000/mystore/edit-product/4/` in order to get to the point in the code you are referring to. – arcee123 Dec 26 '22 at 20:24
  • In your dev server logs, can you provide the list of URLs and HTTP response codes (e.g. 200, 300, 404) that are visited when you submit the form? – DragonBobZ Dec 26 '22 at 20:26
  • here you go: [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 – arcee123 Dec 26 '22 at 20:28
  • Your form seems correct, but try removing the `action` directive altogether, as the default action is to POST to the current URL. – DragonBobZ Dec 26 '22 at 20:29
  • That did it. why did that work? why did "." fail? – arcee123 Dec 26 '22 at 20:30
  • I'm really not sure. "." Seems like it would be fine. I just guessed based on the behavior you were seeing that was your problem. – DragonBobZ Dec 26 '22 at 20:33
  • Is it happening the same in other browsers? – DragonBobZ Dec 26 '22 at 20:37
  • please put that in your answer, and I'll set you as credit. – arcee123 Dec 26 '22 at 20:37
  • it works now in all browsers. – arcee123 Dec 26 '22 at 20:38
  • I was wondering if the "." Resulted in broken behavior in all browsers. – DragonBobZ Dec 26 '22 at 20:42
2

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.

Ivan Starostin
  • 8,798
  • 5
  • 21
  • 39
  • I think ultimately the problem is that "." means current directory. If you treat every URL as a directory by appending a slash, then "." behaves as expected. – DragonBobZ Dec 26 '22 at 20:55
0

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