Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Speeding Up Django Apps

Speeding Up Django Apps

This is for people who are currently building or have finished building a Django app and are interested in minimizing load-time.

It includes optimization tips-and-tricks for:

Django ORM
Django Templates
Third party apps

Paulo Alvarado

February 19, 2017
Tweet

More Decks by Paulo Alvarado

Other Decks in Programming

Transcript

  1. Hi, I’m Paulo Alvarado ✴ django CMS lead developer ✴

    Web developer at ✴ Enjoy hiking and mountains
  2. Example Model class Hotel(models.Model):
 name = models.CharField(max_length=200)
 city = models.CharField(max_length=120)


    postcode = models.PositiveIntegerField()
 manager = models.ForeignKey( to='auth.User', blank=True, null=True ) modified_on = models.DateTimeField(auto_now=True)
 
 def __str__(self):
 return self.name
  3. django ORM ✴ Prefetching ✴ Bulk create ✴ Using _id

    on foreign key fields ✴ Saving / updating ✴ Fetching only what you need
  4. Bulk Create ORM # Ran test create_v1 in 6.549 seconds

    # Ran test create_v2 in 0.477 seconds def create_v1(hotels): for hotel in hotels: hotel.save() def create_v2(hotels): Hotel.objects.bulk_create(hotels)
  5. Using _id on Foreign Key fields ORM def has_manager(hotel): #

    Illustrates the usage of _id to avoid # a new query return bool(hotel.manager_id)
  6. Saving / Updating ORM def set_new_manager_v1(hotel, manager): hotel.manager = manager

    hotel.save() def set_new_manager_v2(hotel, manager): # Call save(update_fields) when you have full control # over the fields that change. hotel.manager = manager hotel.save(update_fields=['manager'])
  7. Saving / Updating ORM def set_new_manager_v3(hotel, manager): # Call queryset.update()

    to avoid triggering # save signals. It will only run an SQL UPDATE. Hotel.objects.filter(pk=hotel.pk).update(manager=manager)
  8. Saving / Updating ORM def set_new_manager_v4(lookup, hotel, manager): # Call

    queryset.update() to avoid triggering # save signals. It will only run an SQL UPDATE. lookup(pk=hotel.pk).update(manager=manager) lookup = Hotel.objects.filter for hotel, manager in hotels_and_managers: set_new_manager_v4(lookup, hotel, manager)
  9. Saving / Updating ORM def update_manager_data(manager, **new_data): for field, value

    in new_data.items(): setattr(manager, field, value) # Not recommended as manager points to a third- # party model which might override the data # that gets written # manager.save(update_fields=list(new_data.keys())) manager.save()
  10. Fetching only what you need ORM # Ran test print_hotel_names_v1

    in 0.175117969513
 # Ran test print_hotel_names_v2 in 0.173953056335 def print_hotel_names_v1():
 for hotel in Hotel.objects.all():
 print hotel.name
 
 def print_hotel_names_v2():
 for hotel in Hotel.objects.only('name'):
 print hotel.name
  11. Fetching only what you need ORM # Ran test print_hotel_names_v3

    in 0.075676202774
 # Ran test print_hotel_names_v4 in 0.0628550052643 def print_hotel_names_v3():
 for data in Hotel.objects.values('name'):
 print data['name']
 
 def print_hotel_names_v4():
 for name in Hotel.objects.values_list('name', flat=True):
 print name
  12. Fetching only what you need ORM 
 
 with open('hotels.csv',

    'wb') as testfile:
 queryset = Hotel.objects.values_list( 'pk', 'name', 'city', ‘postcode' )
 csv_writer = csv.writer(testfile)
 csv_writer.writerow(['ID', 'Name', 'City', 'Post Code'])
 csv_writer.writerows(queryset.iterator())
  13. Templates ✴ Cached template loader (projects only) ✴ Per request

    cached templates (reusable apps) ✴ Fragment caching ✴ More tags less template logic
  14. Cached template loader Templates TEMPLATES = [{
 ... 
 'OPTIONS':

    {
 'loaders': [
 ('django.template.loaders.cached.Loader', [
 'django.template.loaders.filesystem.Loader',
 'django.template.loaders.app_directories.Loader',
 ]),
 ],
 }, 
 }]
  15. Per request cached templates Templates 
 <h2>Hotel List</h2>
 <div class="hotels-list">


    {% for hotel in hotels %}
 {% include "hotels/includes/hotel_info.html" %}
 {% endfor %}
 </div>

  16. Per request cached templates Templates <h2>Hotel List With Cached Template</h2>


    <div class="hotels-list">
 {% for hotel in hotels %}
 {% include hotel_templates.hotel_info %}
 {% endfor %}
 </div>
  17. Fragment caching Templates 
 {% load cache %} <h2>Hotel List

    With Fragment Caching</h2>
 {# 10 minute TTL #}
 {% cache 600 "hotel_list" hotels|length %}
 <div class="hotels-list">
 {% for hotel in hotels %}
 {% include hotel_templates.hotel_info %}
 {% endfor %}
 </div>
 {% endcache %}
  18. Fragment caching Templates 
 {% load cache %} <h2>Hotel List

    Fragment Cache Russian Doll</h2>
 {# 10 minute TTL #}
 {% cache 600 "hotel_list" hotels|length %}
 <div class="hotels-list">
 {% for hotel in hotels %}
 {# 1 day TTL #}
 {% cache 86400 "hotel_list" hotel.pk hotel.modified_on %}
 {% include hotel_templates.hotel_info %}
 {% endcache %}
 {% endfor %}
 </div>
 {% endcache %}
  19. More tags Less template logic Templates {% for plugin in

    plugins %}
 {% include 'cms/plugin.html' %}
 {% endfor %}
  20. More tags Less template logic Templates <script>
 CMS._plugins.push(['cms-plugin-{% if generic

    %}{{ generic.app_label }}-{{ generic.model_name }} -{% if attribute_name %}{{ attribute_name|slugify }}-{% endif %}{% endif %}{{ instance.pk|unlocalize }}', {
 type: {% if generic %}'generic'{% else %}'plugin'{% endif %},
 page_language: '{{ LANGUAGE_CODE }}',
 placeholder_id: '{{ instance.placeholder_id|unlocalize }}',
 plugin_name: '{{ instance.get_plugin_name|default:"" }}',
 plugin_type: '{{ instance.plugin_type }}',
 plugin_id: '{{ instance.pk|unlocalize }}',
 plugin_language: '{{ instance.language|default:"" }}',
 plugin_parent: '{{ instance.parent_id|unlocalize }}',
 plugin_order: '{{ instance.plugin_order }}',{% language request.toolbar.toolbar_language %}
 plugin_restriction: [{% for cls in allowed_child_classes %}"{{ cls }}"{% if not forloop.last %},{% endif %}{% endfor %}],
 plugin_parent_restriction: [{% for cls in allowed_parent_classes %}"{{ cls }}"{% if not forloop.last %},{% endif %}{% endfor %}],
 onClose: {% if refresh_page %}'REFRESH_PAGE'{% else %}{% if redirect_on_close %}'{{ redirect_on_close }}'{% else %}false{% endif %}{% endif %},
 addPluginHelpTitle: '{% trans "Add plugin to" %} {{ instance.get_plugin_name|escapejs }}',
 urls: {
 add_plugin: '{% if add_url %}{{ add_url }}{% else %}{% cms_admin_url "cms_page_add_plugin" %}{% endif %}',
 edit_plugin: '{% if edit_url %}{{ edit_url }}{% elif instance %}{% cms_admin_url "cms_page_edit_plugin" instance.pk %}{% endif %}',
 move_plugin: '{% if move_url %}{{ move_url }}{% else %}{% cms_admin_url "cms_page_move_plugin" %}{% endif %}',
 delete_plugin: '{% if delete_url %}{{ delete_url }}{% elif instance %}{% cms_admin_url "cms_page_delete_plugin" instance.pk %}{% endif %}',
 copy_plugin: '{% if copy_url %}{{ copy_url }}{% else %}{% cms_admin_url "cms_page_copy_plugins" %}{% endif %}'
 } {% endlanguage %}
 }]);
 </script>
  21. More tags Less template logic Templates {% load cms_sample_tags %}


    
 {% render_plugins_js plugins 'en' %}

  22. More tags Less template logic Templates PLUGIN_TOOLBAR_JS = "CMS._plugins.push(['cms-plugin-%(pk)s', %(config)s]);"


    
 def get_plugin_toolbar_js(plugin, request_language, children=None, parents=None):
 plugin_name = plugin.get_plugin_name()
 help_text = ugettext('Add plugin to %(plugin_name)s') % {'plugin_name': plugin_name}
 
 data = {
 'type': 'plugin',
 'page_language': request_language,
 'placeholder_id': text_type(plugin.placeholder_id),
 'plugin_name': force_text(plugin_name) or '',
 'plugin_parent': text_type(plugin.parent_id or ''),
 'plugin_restriction': children or [], …
 }
 return PLUGIN_TOOLBAR_JS % {'pk': plugin.pk, 'config': json.dumps(data)}
  23. More tags Less template logic Templates @register.simple_tag(takes_context=False)
 def render_plugins_js(context, plugins,

    language):
 get_toolbar_js = functools.partial(
 get_plugin_toolbar_js,
 request_language=language,
 )
 plugin_js_output = '\n'.join(get_toolbar_js(plugin) for plugin in plugins)
 return mark_safe(plugin_js_output)