problem in update/delete view

Hi
I am creating a web store and want to have a page that lists a product and customers can leave a comment and ability to edit/delete their comment if they wish to
I manage to make the page for getting their comments. now my problem appears in updating and deleting comments i made the related views but when i go to those pages facing ‘page not found’ error

here is my code
views.py

class GoodsListView(DetailView):
    model = Goods
    template_name = 'goods_list.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['detail'] = Goods.objects.filter(id=self.kwargs['pk']).values()
        context['comment'] = Comments.objects.filter(goods=self.kwargs['pk']).\
            values('goods', 'customer', 'vote_text').order_by('customer')
        context['customer'] = CustomUser.objects.filter(id__in=
            Comments.objects.filter(goods=self.kwargs['pk']).values('customer'))\
            .values('id', 'first_name', 'last_name').order_by('id')
        context['goods_main_band'] = MainBranch.objects.filter(mb_code__in=
            Goods.objects.filter(id=self.kwargs['pk']).values('goods_main_band_id')).values('main_desc')
        context['goods_sub_band1'] = SubBranch1.objects.filter(sb1_code__in=
            Goods.objects.filter(id=self.kwargs['pk']).values('goods_sub_band1_id')).values('sb1_desc')
        context['goods_specification'] = GoodsSpecification.objects.filter(goods_sub_band2__in=
            Goods.objects.filter(id=self.kwargs['pk']).values('goods_sub_band2_id')).values('spec_desc')
        context['goods_properties'] = GoodsProperties.objects.filter(goods_sub_band2__in=
            Goods.objects.filter(id=self.kwargs['pk']).values('goods_sub_band2_id'))\
            .values('goods_intro', 'goods_criticize')
        context['form'] = CommentForm
        return context

    def post(self, request, *args, **kwargs):
        view = CommentPost.as_view()
        return view(request, *args, **kwargs)


class CommentPost(LoginRequiredMixin, SingleObjectMixin, FormView):
    model = Goods
    form_class = CommentForm
    template_name = 'goods_list.html'

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()  # extracting the primery key from url using SingleObjectMixin
        return super().post(request, *args, **kwargs)

    def form_valid(self, form):
        comment = form.save(commit=False)
        self.object = self.get_object()
        form.instance.goods = self.object 
        form.instance.customer = self.request.user
        comment.save()
        messages.info(self.request, 'نظر شما با موفقیت ثبت شد.')
        return super().form_valid(form)

    def get_success_url(self):
        goods = self.get_object()
        return reverse("goods_list", kwargs={"pk": goods.pk})


class CommentUpdate(UserPassesTestMixin, LoginRequiredMixin, UpdateView):
    model = Comments
    form_class = CommentForm
    template_name = 'edit_comment.html'
    fields = ['vote_text']
    success_url = 'goods_list'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        self.object = get_object_or_404(Comments, goods=self.kwargs['pk'])
        context['obj'] = self.object
        context['form'] = CommentForm(instance=self.object)
        return context

    def test_func(self):
        obj = self.get_object()
        return obj.customer == self.request.user


class CommentDelete(UserPassesTestMixin, LoginRequiredMixin, DeleteView):
    model = Comments
    template_name = 'delete_comment.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data()
        context['g_name'] = Goods.objects.filter(id=self.kwargs['pk']).values('name')
        context['g_comment'] = Comments.objects.filter(goods=self.kwargs['pk']). \
            values('vote_text').order_by('customer')
        return context

    def test_func(self):
        obj = self.get_object()
        return obj.customer == self.request.user

urls.py

urlpatterns = [
    path('about/', AboutPageView.as_view(), name='about'),
    path('?/', GoodsSearchView.as_view(), name='gsview'),
    path('?/<int:pk>/', GoodsListView.as_view(), name='goods_list'),
    path('?/<int:pk>/edit_comment/', CommentUpdate.as_view(), name='edit_comment'),
    path('?/<int:pk>/delete_comment/', CommentDelete.as_view(), name='delete_comment'),
]

models.py

class Goods(models.Model):
    goods_main_band = models.ForeignKey(
        MainBranch, to_field='mb_code', on_delete=models.CASCADE,
        verbose_name='کد دسته اصلی', default=0
    )
    goods_sub_band1 = models.ForeignKey(
        SubBranch1, to_field='sb1_code', on_delete=models.CASCADE,
        verbose_name='کد دسته فرعی 1', default=0
    )
    goods_sub_band2 = models.ForeignKey(
        SubBranch2, to_field='sb2_code', on_delete=models.CASCADE,
        verbose_name='کد کالا', default=0
    )
    goods_properties = models.OneToOneField(
        GoodsProperties, on_delete=models.CASCADE,
        verbose_name='مشخصات کالا', null=True, blank=True
    )
    goods_specifications = models.OneToOneField(
        GoodsSpecification, on_delete=models.CASCADE,
        verbose_name='معرفی کالا', default=0, null=True, blank=True
    )
    name = models.CharField(verbose_name='نام کالا')
    image = models.ImageField(upload_to='photos/%Y/%m/%d', null=True, blank=True, verbose_name='تصویر کالا')
    quantity = models.PositiveIntegerField(default=0, verbose_name='تعداد موجود')
    price = models.DecimalField(
        decimal_places=0, max_digits=12, default=0,
        verbose_name='قیمت کالا'
    )
    discount = models.SmallIntegerField(null=True, blank=True, verbose_name='میزان تخفیف')
    seller = models.ManyToManyField('Seller', verbose_name='کد فروشنده')

    @property  # getting all comments related to a specific good
    def get_comments(self):
        goods_comment = self.comments.all()
        return goods_comment

    def __str__(self):
        return self.name

class Comments(models.Model):
    goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='کد کالا', related_name='comments')
    customer = models.ForeignKey(CustomUser, on_delete=models.CASCADE, verbose_name='کد مشتری',)
    vote_text = models.TextField(verbose_name='متن نظر', help_text='متن نظر را وارد کنید')
    vote_point = models.SmallIntegerField(null=True, blank=True, verbose_name='رای')
    vote_date = models.DateTimeField(auto_now=True, verbose_name='تاریخ ثبت رای')

    def __str__(self):
        return self.vote_text

I’ll appreciate the solution given in class-based approach
thanks

The error message is saying that you don’t have a Comment with pk == 6.

This means the most likely cause is that the page generating the links aren’t creating the links properly. That’s the view and template we would need to see here.

So, what view generated the page containing the link to CommentUpdate? What does it’s template look like for generating those links?

Hi MohammadAsadi4,

It appears that your current URL configuration in the urls.py file is directing requests to the CommentUpdate view. However, there’s a mismatch between the expected object type in the view and the object type represented by the <int:pk> parameter in the URL.

Specifically, in your CommentUpdate view, you’ve defined model = Comments, which implies it’s expecting a “Comments” object. However, the <int:pk> parameter in your URL path is indicating a “Goods” object, which is causing the view to attempt to fetch a “Comments” object with a specific primary key (e.g., pk=6).

class CommentUpdate(UserPassesTestMixin, LoginRequiredMixin, UpdateView):
    model = Comments
    # rest of the view code

As a result, the view might not find a matching “Comments” object and raise an “Http404” error.

To resolve this issue, you should create new URL paths for your comments views with the appropriate object type, like this:

    ...
    path('edit_comment/<int:pk>/', CommentUpdate.as_view(), name='edit_comment'),
    path('delete_comment/<int:pk>/', CommentDelete.as_view(), name='delete_comment'),
    ...
]

Here, <int:pk> represents a “comments” object’s primary key.

I’m sorry, but I disagree with this analysis. There is nothing fundamentally wrong with the url structure as defined.

Yes, the wrong PK is being supplied - but without seeing the template being rendered for the originating view, any identification of a cause is just a guess.

Specifically:

There is no support for saying this from the information provided so far.

Parameters are arbitrary, and are interpreted by the view. The other components of a url are also arbitrary and do not “indicate” anything.

KenWhitesell I appreciate your reply, and you’re right that without seeing the template, we can’t definitively identify the cause. Also you’re correct that the parameters in the URL are arbitrary and interpreted by the view.

However, I made an assumption based on the error message picture provided by MohammadAsadi4. The URL pattern “goods/?/<int:pk>/” resolves to GoodsListView(which is actually a generic DetailView) that is responsible for fetching a Goods object. Given this context, I considered the possibility that MohammadAsadi4 might inadvertently be sending a Goods object’s primary key for the “goods/?/<int:pk>/edit_comment/” pattern, which could be leading to the issue.

I absolutely agree here. However, the resolution is to fix the pk being generated in the existing URL and not to create or alter the URL structure.

This:

does not address that underlying issue. It’s most likely either an instance of a {% url ... %} tag in the template or a reverse call in the view that needs to be changed, not the url.

1 Like

Thanks for all your replies

Actually issue that Ken mentioned clicks something in my mind and you are right ken 6 is goods pk so as csAfter40 mentioned i should pass comments pk so i change my urlpattern like below

urlpatterns = [
    path('about/', AboutPageView.as_view(), name='about'),
    path('?/', GoodsSearchView.as_view(), name='gsview'),
    path('?/<int:pk>/', GoodsListView.as_view(), name='goods_list'),
    path('?/<int:pk>/edit_comment/<int:cpk>', CommentUpdate.as_view(), name='edit_comment'),
    path('?/<int:pk>/delete_comment/<int:cpk>', CommentDelete.as_view(), name='delete_comment'),
]

and here is the template from which customer redirects to good’s detail page and editing/deleting comments as well

<h4>نظرها</h4>
        <div class = "card-footer">
            {% for comment in goods.comments.all %}
             <p>
                <span class = "font-weight-bold">{{comment.customer}} &middot;
                </span>
                {{comment.vote_text}}
                <a href="{% url 'edit_comment'  'pk':goods.pk 'cpk':comment.pk %}">ویرایش</a> |
                <a href="{% url 'delete_comment'  'pk':goods.pk 'cpk':comment.pk%}">حذف</a>
            </p>
            {% endfor %}

but the the problem still not solved

Your CommentUpdate view is still trying to fetch the Comments object using the ‘pk’ in your url, not ‘cpk’. You either need to set ‘pk’ as comment.pk in your template url or set your pk_url_kwarg as ‘cpk’ in your view . I think the below code could work.

class CommentUpdate(UserPassesTestMixin, LoginRequiredMixin, UpdateView):
    pk_url_kwarg = 'cpk' 
    model = Comments
    #rest of the view code
1 Like

Or, instead of the above, do you really need the Goods pk in the url?

You may be able to do this:

<a href="{% url 'edit_comment'  'pk':comment.pk %}">ویرایش</a> | 
<a href="{% url 'delete_comment'  'pk':comment.pk%}">حذف</a>

and not change your urls.

Thanks for your replies
I changed those parts that csAfter40 mentioned and it seems the problem solves but another one appears when i go to edit_comment page i face
‘dict’ object has no attribute ‘_meta’
error and i do not understand what is all about
here is my views.py

class CommentUpdate(UserPassesTestMixin, LoginRequiredMixin, UpdateView):
    model = Comments
    form_class = CommentForm
    template_name = 'edit_comment.html'
    pk_url_kwarg = 'cpk'

    def get(self, request, *args, **kwargs):
        context = super().get(request, *args, **kwargs)
        data = Comments.objects.filter(goods=self.kwargs['pk'])        
        context['form'] = CommentForm(instance=data)
        return context

    def get_queryset(self):
        queryset = super().get_queryset()
        return queryset.filter(goods=self.kwargs['pk']).values()

    def get_context_data(self, **kwargs):
        context = super().get_context_data()
        data = Comments.objects.filter(goods=self.kwargs['pk'])
        context['form'] = CommentForm(instance=data)       
        return context

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().post(request, *args, **kwargs)

    def form_valid(self, form):
        comment = form.save(commit=False)
        self.object = self.get_object()
        comment.save()
        messages.info(self.request, 'نظر شما با موفقیت ویرایش شد.')
        return super().form_valid(form)

    def test_func(self):
        obj = self.get_object()      
        return obj.get('customer_id') == self.request.user.id

The get_queryset method must return a queryset, not a list of dict.

Also note that:

is going to return a queryset, and not an individual instance of Comments.

You’re also doing a lot more work than necessary in the methods you are overriding. I suggest you familiarize yourself with the inner workings of an UpdateView to understand what it does. I recommend the Classy Class-Based Views site for browsing the source code, and the CBV diagrams page to help with understanding the sequence of events.

Specifically, for example, you have multiple cases where you set self.object from self.get_object, but in almost every case, you don’t need to do that - that’s part of the parent class’ responsibility.

Thanks for your answer
Actually I was looking for some guide about class based views that clarifies the sequence and implementation