Python Properties can make method calls visible to Django templates!
I have been developing a website for my wife as she catalogs every brewery, cidery, distillery, meadery, and winery in Colorado. Using Django has been a dream – easy interactions with the database, and templates make rendering data to HTML simple.
I wanted to give users the ability to view information about a place by entering its name in the URL. This is not only nice for humans, but search engines will rank you slightly higher if you have coherent urls.
Say a user wanted to view information about a place called Fresh. Then the user could navigate to “/place/Fresh”.
This works well for most entries, but plenty of businesses have special characters and spaces in their name. Trying to navigate to “/place/Fresh Brew ‘n Stuff!”, for instance, doesn’t translate very well to a navigation request, and lookups on models can be unreliable with the added syntax.
Here’s the basis of the model that stores the name of each business in the database:
class Place(models.Model): name = models.CharField(max_length=125, unique=True) ...
And here is the url-pattern that users will be directed to:
url(r'^place/(?P<name>[\w|\W]+)$', PlaceInfoView.as_view(), name='place_profile')
Each instance of Place will need a way to generate a safe url-encoding of its name.
That’s easy. Urllib solved this a long time ago. These methods added to Place handle the encoding as well as the decoding and retrieval:
import urllib ... def get_safe_url_name(self): return urllib.parse.quote(str(self.name)) @classmethod def find_place_from_safe_url_name(cls, name: str): return Place.objects.get(name__iexact=urllib.parse.unquote(name))
Horray! But there’s a problem: Django templates can’t call either method. They can only access variables from a class. (Update 12/06/16: Thanks to many eager replies, I’ve learnt methods can be called from within Django templates, although I hold the position that this information wasn’t well documented. Regardless, calling “place.get_safe_url_name” from within a template makes it look like we’re accessing a variable, not calling a function. It’s because of this I still believe a property is a more correct solution.)
Admittedly, I’ve done the lazy, round-about, hacky solution of zipping each instance with its safe-encoding and iterating over each tuple in the template. That’s messy and unmaintainable though. Months later, the template and its respective view will confuse developers, at least for a short time.
This is a perfect time for a property!
Properties, among other things, allow getter and setter methods to be called as variables of an instance. Here’s what was added to the Place model:
@property def safe_url_name(self): return self.get_safe_url_name()
Now in a Django template, a URL using safe encoding can be generated:
<a href="{% url 'place_profile' name=place.safe_url_name %}">
And it can be used in Python code:
url_to_redirect = reverse('place_profile', kwargs={'name': place.safe_url_name})
There! Using a property, I can call a function from a template and get the relevant information I need to link pages together using a standard encoding.
Simple.