Ilya Petrov
Ilya Petrov

Reputation: 43

How to avoid calling get_absolute_url each time on page (django)?

Everybody tells that it is very good practice to use get_absolute_url in templates. But in my case it causes lots of the same queries to the database on the single page. Here is structure of urls I have to develop (I can't change it because the customer has already working web-site, and Google is not going to like it if I change urls) - mysite/category/subcategory/product_slug.html Here is the code of url patterns:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^(?P<parent_category_slug>[\w-]+)/(?P<category_slug>[\w-]+)/(?P<slug>[\w-]+)\.html$', views.ProductDetailView.as_view(), name='product_detail'),
    url(r'^(?P<parent_slug>[\w-]+)/(?P<slug>[\w-]+)$', views.ProductListView.as_view(), name='products_of_category'),
    url(r'^(?P<slug>\w+)$', views.SubCategoryListView.as_view(), name='sub_category'),
    url(r'^$', views.CatalogIndexListView.as_view(), name='index'),
]

And here is code of get_absolute_url in Product model:

def get_absolute_url(self):
    return reverse('product_detail', kwargs={'slug':self.slug, 'parent_category_slug':self.product_category.category_parent.slug,
                                             'category_slug':self.product_category.slug})

So, when I go at mysite/category/subcategory, I see all products belong to the subcategory. This is a list (actually table, with images, titles and so on). And all the images as well as titles must be urls to the product. Here is piece of code in template

e {% for product in products %}

                    <tr>
                        <td class="product_name_list">
                            <a href="{{ product.get_absolute_url }}">{{ product.product_name }}</a>
                        </td>
                        <td class="product_article_list">{{ product.product_article }}</td>
                        {% if product.product_main_image  %}
                            <td class="product_image_list"><a href="{{ product.get_absolute_url }}" ><img src='{{ product.product_main_image.url}}' alt=""></a></td>
                        {% else %}
                            <td class="product_image_list"><a href="{{ product.get_absolute_url }}" ><img src='{% static "images/empty.gif" %}' alt=""></a></td>
                        {% endif %}

                        <td class="product_request_list"><a href="#">Запросить</a></td>
                    </tr>

             {% endfor %}

So, in result, I have really a lot of queries to database, because get_absolute_url is called repeatedly.

Please, help me to avoid this. I've tried to set default Manager class with 'get_related()', but it was stupid, obviusly it did not help, because every instanse calls method get_absolute_url again and again.

Thanks in advance!

Upvotes: 1

Views: 483

Answers (2)

dela
dela

Reputation: 11

Decorating get_absolute_url() as a @cached_property doesn't work if this method is called from Python code – a property must not be written in call syntax (with parentheses). In templates this doesn't matter as there are no parentheses anyway. The following suggestion caches the URL but requires no further changes, neither in views nor in templates:

@cached_property
def absolute_url(self):
    return reverse(...)

def get_absolute_url(self):
    return self.absolute_url

Should the URL ever change on an existing object, the cached value can be cleared with del obj.absolute_url.

Upvotes: 1

v1k45
v1k45

Reputation: 8250

You can use django's cached_property decorator to solve this

from django.utils.functional import cached_property

# You can either use it convert `get_abolute_url` method to property
@cached_property
def get_absolute_url(self):
    return reverse(
        'product_detail', kwargs={
            'slug':self.slug,
            'parent_category_slug':self.product_category.category_parent.slug,
            'category_slug':self.product_category.slug})

# or decorate the method with different name so that you can use both

cached_absolute_url = cached_property(get_absolute_url)

By this you'll be able to use both,

object.get_absolute_url()

object.cached_absolute_url

cached_property caches the value of the method so that when you call it again, instead of running through the whole method, it directly returns you the cached value.

Upvotes: 2

Related Questions