Robert F.
Robert F.

Reputation: 141

'CategoryDetail' object has no attribute '_state'

I want to get the properties by categories.

When I try to reach for example : http://127.0.0.1:8000/category/Berlin the following error comes:

'CategoryDetail' object has no attribute '_state'

What am I doing wrong?

Any help will be highly appreciated.

models.py

from django.db import models
from django.urls import reverse

# Create your models here.

class Property(models.Model):
    title = models.CharField(max_length=140)
    description = models.TextField()
    rooms = models.PositiveIntegerField()
    bed_rooms = models.PositiveIntegerField()
    land_size = models.PositiveIntegerField()
    slug = models.SlugField()
    living_size = models.PositiveIntegerField()
    build_year = models.PositiveIntegerField()
    price = models.PositiveIntegerField()
    category = models.ForeignKey(to='Category', null=True, related_name='categories', on_delete=models.SET_NULL,)

    def get_absolute_url(self):
        return reverse('properties:PropertyDetail', kwargs={'slug': self.slug})

    def __str__(self):
        return '{} - {}'.format(
            self.title, self.build_year
        )

class Category(models.Model):
    name = models.CharField(max_length=150)
    slug = models.SlugField()


    def get_absolute_url(self):
        return reverse('properties:CategoryDetail', kwargs={'slug': self.slug})



    def __str__(self):
        return '{}'.format(self.name)

views.py

from django.shortcuts import render
from .filters import PropertyFilter

# Create your views here.

from django.views.generic import (
    TemplateView, DetailView, ListView
)

from .models import Property, Category

class HomePage(TemplateView):
    template_name = 'home.html'


class PropertyList(ListView):
    model = Property


class PropertyDetail(DetailView):
    model = Property


class CategoryList(ListView):
    model = Category


class CategoryDetail(ListView):
    queryset = Category.categories
    template_name = 'categorydetail.html'


def search(request):
    property_list = Property.objects.all()
    property_filter = PropertyFilter(request.GET, queryset=property_list)
    return render(request, 'prop_list.html', {'filter': property_filter})

urls.py

from django.urls import path

from .views import  (PropertyList, PropertyDetail, HomePage, CategoryList, CategoryDetail)

app_name = 'properties'

urlpatterns = [
    path('', HomePage.as_view(), name='home'),
    path('categories', CategoryList.as_view(), name='categories'),
    path('category/<slug:slug>', CategoryDetail.as_view(), name='CategoryDetail'),
    path('properties', PropertyList.as_view(), name='PropertyList'),
    path('property/<slug:slug>', PropertyDetail.as_view(), name='PropertyDetail'),
]

project urls

"""config URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from realestate import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('search', views.search, name='search'),
    path('', include('realestate.urls', namespace='properties')),
]

Upvotes: 1

Views: 1473

Answers (3)

Akaisteph7
Akaisteph7

Reputation: 6554

For some reason my model was just having an issue being called twice to do an action on a ForeignKey. Simply refetching the object before trying to reuse it fixed my issue.

Upvotes: 0

cezar
cezar

Reputation: 12032

Please adapt the following line in the class Property:

category = models.ForeignKey(to='Category', null=True, related_name='categories', on_delete=models.SET_NULL,)

to this:

category = models.ForeignKey(
    to='Category',
    null=True,
    related_name='properties',
    on_delete=models.SET_NULL,
)

The change is in bold. The reason for the change is that you want to get properties for a given Category. Making a query like Category.categories is confusing for the reader.

The related_name which is now properties can be accessed from a Category object. The way how you do it in the class CategoryDetail:

queryset = Category.categories

even if adapted to:

queryset = Category.properties

can't work. It is because it would call the related descriptor ReverseManyToOneDescriptor which is of no use here.

You can call the properties for an object Category. An example would be:

Category.objects.get(pk=1).properties.all()

The first part Category.objects.get(pk=1) will return a single object Category and in the second part properties.all() you call the manager properties on the object Category. With the method all() you retrieve a QuerySet containing all properties for the given Category object.

Remember that using something like Category.objects.all().properties.all() won't work. Category.objects.all() will return a QuerySet which has no attribute properties.

With this in mind the best approach would be as suggested by @Alasdair to use DetailView. You want to list all Properties for a Category and that confuses you a little bit. However you're still displaying the details for a Category. Using a DetailView for the Category here is a much cleaner and nicer approach.

Having just simple:

class CategoryDetail(DetailView):
    model = Category

will do the job. The default template name will be category_detail.html and you don't need to specify it explicitly. Just be consistent with the other classes.

The internal logic of the detail view will retrieve a single object Category and in the template you can access the properties for this object:

{% for property in object.properties.all %}
    {{ property }}
{% endfor %}

Instead of object.properties.all you can use category.properties.all, which is more verbose and improves the readability.

Should you wish to change that, do this for example:

class CategoryDetail(DetailView):
    model = Category
    context_object_name = 'ctgr'

{% for property in ctgr.properties.all %}

I hope this can bring you forward.

Upvotes: 1

Alasdair
Alasdair

Reputation: 309089

First, change the related_name to properties as @cezar suggested, since it is used to get the related properties for a category.

category = models.ForeignKey(to='Category', null=True, related_name='categories', on_delete=models.SET_NULL,)

I suggest you use a DetailView for category,

class CategoryDetail(DetailView):
    model = Category
    template_name = 'categorydetail.html'

Then you can use the reverse relationship to access the properties in the template:

{% for property in category.properties.all %}

If you really want to use a list view for CategoryDetail, then you need to override get_queryset and use the slug. For example you could do:

from django.shortcuts import get_object_or_404

class PropertiesForCategory(ListView):
    template_name = 'categorydetail.html'

    def get_queryset(self):
        category = get_object_or_404(Category, slug=self.kwargs['slug']
        return category.properties.all()

Then in the template you would loop through the queryset with:

{% for property in object_list %}

Upvotes: 1

Related Questions