Laravel vs Django Web Framework

There are some comparisons out there on the web, but they appear to be shallow. Most of them just cover performance (Django wins), security (Django wins), learning curve (Django wins) and API (django loses, even though there is excellent API support in the form of a well maintained package called Django Rest Framework).

Let’s try to find out the real differences by looking at the different steps needed to build a simple blog application.

Disclaimer: I know Django quite well and Laravel not at all. Which means I am biased. Please collaborate via to improve the Laravel part of the comparison.


Django provides a huge community and a very strong open source code base. It is probably safe to say that the underlying programming language Python has everything. There are many Python libraries that are well tested and ready for production.

Pypi is a very strong package manager with very good conflict management and dependency resolving. It is straightforward to use a virtual environment to create local, customized environments for different projects (similar to NPM and node modules) but in a less crazy way.

Django itself has a huge package directory with very useful comparison pages like – it is simple to see which packages should be chosen for a specific job.

IDEs are readily available for example Pycharm including break point debugging and full inspection out of the box. Django has its own development server that runs as a CLI command out of the box. It’s also hasslefree to dockerize a local dev env (including debugging!)

Laravel has a huge community and a pretty strong open source code base. The underlying programming language goes back to Adam and Eva. There are many libraries. Dependencies are handled in composer. Finding a package appears to be a bit of a hassle as doesn’t have browsable categories or comparison grids.

IDEs are readily available including breakpoint debugging for example PhpStorm. For debugging Xdebug needs to be installed and configured and the official PhpStorm docs even mention installing a browser extension. This appears to be quite a hassle. For dockerizing a local dev env, there appears to be a working cookbook.

Laravel can be run in development mode locally via CLI command php artisan serve out of the box.


Django lets you define a model with different fields. Then it provides a CLI command to create migrations automatically. ./manage migrate will then apply the new model or any subsequent changes to the database. Upon changing the model, i.e. altering the properties of a model field, Django will detect this change and generate a corresponding migration.

Laravel allows you to create a migration file but its empty. You have to fill it with things you want to migrate yourself. Laravel does not support creating database migrations from the modal automatically. This means that your model code lives in two places (1. migrations, 2. model) and they have to be kept in sync manually.


Django ORM is taking care of all SQL things for the programmer. Query building is chainable like Animal.models.filter(color='black').filter(legs=4).filter(owner__email="").distinct(). Its Q class allows to build sophisticated encapsulated queries without touching SQL. Also note the ease with which the query can span foreign relations (owner__email points to the email field of the foreign relation of the owner field, for example a User model)

Laravel has a sophisticated ORM which allows custom queries and managers.

TBD: I was not able to quickly find a simple straightforward solution on how to filter via a foreign key relation’s property like in Django.

Admin Interface

An often overlooked feature, Django provides an admin interface out of the box. It has support for translation via 3rd party apps.

It requires zero configuration as it inspects the model definitions. It supports CRUD permissions out of the box per model and has mobile optimized widgets for all fields, including dates, searchable many-to-many relations, searchable auto completion for foreign keys. The django admin package is built on top of Django’s templating system, so its look and feel is extendable, allowing for example DjangoCMS to build its entire customly styled admin interface on top of it, without changing core functionality. Django Admin is configured by extending the package’s classes with properties such as list_display = ('first_name', 'last_name') in the module’s file, by default, no files are generated into your project.

Laravel has Laravel Nova as the official admin panel, it looks great. It has support for translation via 3rd party apps.

There are also some 3rd party admin packages that offer a lot of functionality, for example For example, it is nice to have an image upload widget that allows cropping.

Generally, in Laravel such packages require generating scaffolding code which then lives in your project. Changing admin behaviour requires the programmer to write PHP code, inside the scaffolded code.


Django has its own template language or Jinja2 can be used, both are hierarchical and use the extension strategy. Both are pretty mutch self explanatory. Some programmers dont like the django template language because it is limited (it only supports custom template tags and filters). But in my experience Django Template’s support for include together with with (context) solves it all. Jinja2 has support for components (called macros) and is said to be more powerful, so feel free to switch to jinja2 via some simple configuration in

Laravel uses hierarchical Blade templates with the extension strategy. Blade has components which allows to quickly create custom blocks.

Frontend Support

Django has no special tools to handle CSS / SASS or JS / TS. In my opinion, that’s a plus, because it forces programmers to use state of the art frontend tools like webpack and keep backend and frontend strictly separated.

Laravel has Laravel Mix which appears to be a laravel-flavoured frontend setup. It’s probably not suitable for professional use (correct me if I am wrong). It doesnt appear to support any built-in chunking though. Vuejs has its own tooling and it’s probably simpler to just use the plain vanilla react or vuejs setup strictly separated from laravel.

Static Files Support

Django has first-class support for static files and media files. Django’s collectstatic command allows to keep static files inside static/ folders within the single modules and apps (possibly inside 3rd party modules) and still be able to serve them from one single static file directory (or S3 bucket). That’s very smart because 3rd party modules dont have to be part of the code base of the project and can still contribute their static files in a fully automated way.

Django does have one more trick up its sleeve: ManifestStaticFilesStorage is a storage driver that automatically hashes all static files. This means, that it is very easy to have webpack build the app.js (and app.css bundle, or also multiple bundles) without hashes and then have them hashed by ManifestStaticFilesStorage so you can have a perfect caching solution with far-future headers enabled on the web server for the static file directory, including all server-rendered images.

From what I see there are no special tools for handling static files in Laravel.

i18n and localisation support

Django has extreme localisation and i18n support out of the box. django admin offers a translation interface for models right out of the box with well tested and widely compatible django-parler support. Also it has number formatting localisation support.

Django has its own wrapper around gettext / .po files (CLI commands makemessages and compilemessages. It allows to translate strings anywhere in Python code or in templates.

Laravel has simple i18n support and no gettext / .po support out of the box. I suspect that i18n might not be very performant.

Laravel has to fall back to third party tools like for format localisation. Are there disadvantages? For example how is a date formatted in a template? Does this require additional code in the template?

TBD: How can a model be translated in a laravel admin out of the box?


Django documentation and tutorials are huge. It allows to quickly get going, even for a complete beginner. At the same time it has all the details for the pros to quickly look up some detail.

I find Laravel’s documentation a bit on the minimalistic side. It’s hard to get detailled information about specific configuration properties. A lot is left open to the developer. This doesnt incentivize the developer to follow best practise coding approaches and standards. I dare to say that it’s much easier to write bad code in Laravel than it is in Django.

Other Things

Django supports mailing, events and caching quite well.

Laravel supports mailing, events and caching quite well.

Task Queues and Scheduling

Django has asyncronous task support via a 3rd party library (Celery). It’s not so easy to set up and requires some ground work and configuration, especially for deployment and monitoring. Monitoring is mostly done via Flower, but it’s a bit outdated and sometimes a bit useless.

Laravel has built in asyncronous task processing built in via its Queues. This looks like a robust, performant and lean solution. The monitoring solution appears to be awesome:

Authentication and oAuth

Django supports oauth (also in combination with the REST API via Django Rest Framework) quite well.

Laravel has Socialite and Passport and appears to support oauth (also in combination with the REST API) quite well and out of the box. This looks like a robust, performant solution.


Django has good support for quick and easy implementation of a full-text search via django-haystack it can connect to powerful search engines like SolrElasticsearchWhooshXapian but also has a simple database driven backend.

Laravel has Scout which appears to support full-text search quite well, quickly and easily and out of the box. This looks like a performant, robust solution.

Browser Automation / Integration Testing

Django has a very good testing framework out of the box which supports selenium quite well.

Laravel has Laravel Dusk which installs via PHP composer and appears to have a quite lean setup via ChromeDriver. This looks like a performant, robust and easy solution.

Django Signals & django-fieldsignals

default_app_config = 'app.apps.MyApp'

# -*- coding: utf-8 -*-
from django.apps import AppConfig

class MyApp(AppConfig):
    name = 'my_app'
    verbose_name = 'MyApp'

    def ready(self):
        from . import signals

@receiver(post_save_changed, sender=models.WorkEntry, fields=[
], dispatch_uid="update_gitlab_time_spent_updated")
def update_time_spent_on_changed_issue(sender, instance, changed_fields, **kwargs):
     for field, (old, new) in changed_fields.items():
         if == 'issue':
             # do something

A simple and solid approach at deferring javascript execution

In most cases javascript code should be deferred so it doesnt block the page loading process.

Javascript files can be deferred simply like this, the execution order of deferred scripts will be strictly kept by browsers in the order of appearance in the HTML file:

<script defer="defer" src="vendor.js"></script>
<script defer="defer" src="app.js"></script>

However projects might require inline js code. That code will execute before the deferred scripts – which might throw errors if the inline js code depends on libraries loaded via deferred script tags. Inline js code can be deferred like described on stackoverflow:

    window.addEventListener('DOMContentLoaded', function() {
        (function($) {

defer was introduced before async. Its execution starts after parsing is completely finished, but before the DOMContentLoaded event. It guarantees scripts will be executed in the order they appear in the HTML and will not block the parser.

Once all of your js is either triggered by DOMContentLoaded or has the `defer tag it is easy to manage and maintain control of the execution order.

Vuejs + Google Spreadsheets as (simple) Backend

This is a proof of concept to get data from a google spreadsheet via frontend only and display it in a vuejs app, deployed via gitlab pages:

The purpose is to use Google Spreadsheet as a “cheap” data source for a widget on a website.

The project source is public:

Interactions and Goal Tracking with Google Tag Manager – Technical Best Practices for Developers

As you know, Google Tag Manager is a tool that helps Online Marketing people to set up different kind of user interaction and conversion tracking without the help of developers. This separation of roles is beneficial for both developers (no tedious updates of click events) and marketing staff (no dependency to IT), however it only works if web developers prepare the website according to best practices.

This is a document for developers to understand better the life of an Online Marketing staff that is in charge of setting up and maintaining Google Tag Manager click events.

So what do Online Marketing people want to do with Google Tag Manager?

Here is a typical Google Tag Manager (GTM) use case:

  • GTM managers would like to set up the tracking of a range of goals in Google Analytics so that business can see interaction and conversion trends and online marketing can optimise their campaigns based on these signals.
  • GTM managers would like to set this up in Google Tag Manager so that online marketing and business can wire the conversion and interaction signals to their tags and update the wiring quickly and independently from website deployment schedules.

Typical GTM Troubles that Online Marketing Managers have

Below is a list of technical things that sometimes make the life of a GTM manager tough.

1) Link Clicks Triggers don’t fire

  • Sometimes, GMT managers set up click triggers for buttons and the click triggers don’t fire.
  • Here is an example of a typical click trigger on a submitt button on a page like /booking?date=2019-01-01:
  • This is the button:
  • Here is the markup of the button:
  • However the GTM manager notices that this click trigger doesn’t fire.
  • Note: The button click tracking does fire if GTM managers configures the trigger with the button text only (Click Text) == ‘SEND REQUEST’
  • In this specific case GTM managers would like to be able to use the parent element (button with type == submit) because this is less ambiguous and more bullet proof and simpler to set up and maintain.
  • In such cases, when this doesn’t work as intended, maybe this is the cause:
  • Developers can help GTM managers having a simpler and more solid tracking setup by letting click events bubbling up in all cases.

2) No ids or distinguishing classes for important interaction elements

  • Currently important interaction elements such as buttons are not easily identifiable by business / online marketing staff when working with GTM.
  • An ID or a class name like “booking-date-time-step-continue” would simplify tracking setup.
  • Developers can help us simplify tracking by proactively setting ids or identifiable classes on important interaction elements.

3) No dataLayer information for transactional events

  • For some tracking cases GTM managers would like to measure more than a click. Example: a booking. In this case, it would be safer to get a signal from the backend that the booking has correctly been processed and is valid. This helps us prevent wrong trackings, for example if GTM managers would track the click on the ‘book now’ button, and there were form validation errors, GTM managers would still track a successful booking even if the website would not.
  • In order to help us track conversions correctly, you can use Google Tag Manager’s dataLayer.push() method.
  • Example:  dataLayer.push({'event': 'event_name'});
  • See documentation here:
  • Use a speaking and unique event name such as “booking-button-send-request” (similar to html ids)
  • You can push additional information by adding arbitrary properties and values to the object.
  • It helps the GTM managers if you document these event names and additional attributes in a Google spreadsheet that you share with them.

djangocms / djangocms-filer: 500 server error: failed to generate icons for file

This is a nasty error, because django-filer just returns this error message in json format when uploading a file via FilerImageField or the djangocms-filer admin interface.

In order to find out the true reason behind this, set the following env vars to true:


FILER_DEBUG will return a real python exception in the response to the async request that handles the file upload.

In my case, the true error was: decoder jpeg not available which I handled via

That helped!

How Google Ads spends your money behind your back

In recent months, Google has increased efforts to automate many aspects of Google Ads, for example the display targeting. By doing so manual controls have been hidden or removed from Google Ads or default settings have been changed. This is not always in the best interest of the advertiser. Below I will collect cases where Google has started to take decisions over the head of the advertiser, and how advertisers can take back control.

Display Native Apps Exclusion

Many advertisers see little to no return on their ad spend invested on native app placements on the Google Display Network. Meanwhile, for Google it’s a fast growing market as more and more impressions are generated in native apps, mostly free-of-charge games.

In earlier, happier days, all in-app ads could be disabled by excluding the placement . Google has phased this convenient possibility out in 2018. This article describes a workaround, but the setting has become harder to find and it’s not possible to exclude all native app placements as one anymore.

Display automatic targeting

If advertisers pay close attention they can see that by default, much of their display ad spend goes into something called Display Automated Targeting. This inflates media spend considerably which is especially misleading in Remarketing campaigns where an advertiser wants to target exclusively users that have visited the website before.

Here is how to deactivate Display automatic targeting for Remarketing (and other) campaigns.

Responsive Images and Safe Areas

Sometimes a designer or a content editor wants images, videos or more advanced content sections such as image sliders to take the full width of the browser window or even the full screen (height and width).

Full width or full screen content has many advantages from a design point of view and is often used to show off great videos or images in the header of a page. Homepages often use a full screen hero section:

These full width elements normally have a background video or image which is set to cover all the available space, given a fixed height (for example 600px height).

This means, that depending on the browser windows size, a part of the background image will be cut off.

Background images are often centered. This means, that if the browser window is wider than the background image width, an equal part on the top and bottom of the background image will be “cut off” (= not visible). If the browser window is less wide than the background image, the background image will still try to cover the given height of the full-width section. For this, it has to sacrifice space in the horizontal dimension. The background image will therefore be cut off on the left and right, like, for example on this iphone X screen:

In the real world, users will come to see your beautiful full width section on many different screen and browser sizes. Therefore, many of them will never see all of your beautiful background picture, but always just parts of it.

That’s where safe areas come in. A safe area is the part of the image that will always* show, whatever the user’s device or screen size. Here is an example of the definition of a safe area for a specific website project:

Content editors and designers should be briefed accordingly, so that they know how to resize and crop their images, so that the important part of the image is inside the safe area. Safe area briefings for different full-width sections on the website are therefore often created by project managers and web programmers.

Here you are a template that you can download and use for your own purposes:

I hope I could explain the complexities of using full-width images in this articles. If you have any questions don’t hesitate to comment or write to me.

*In practice, of course, there will always be users that wont even see the safe area, because, for example, they use a very old Blackberry phone. However in web design its best practice to set a compatibility policy and then ignore devices and browsers outside of that policy for cost and efficiency reasons.

Django<2 and sqlite3 Incompatibility

Older versions of django are currently not compatible with sqlite>3.25. More information here: – Problems occur when trying to load a fixture via `./ loaddata` or in some cases when using a database created in an old version of sqlite3 django throws a segmentation fault.

Uninstall any existing homebrew versions of sqlite3 via brew uninstall sqlite3

Then install sqlite 3.35:

brew install

Django / DjangoCMS – custom language

We had to create custom languages in Django for two different versions of the Chinese language: yue (Mandarin Chinese) and cmn (Cantonese Chinese)

This is really simple in Django with gettext, as the default languages can just be overriden:

    ## Customize this
    ('en', "English"),
    ('cmn', "Mandarin Chinese"),
    ('yue', "Cantonese Chinese"),

    ## Customize this
    1: [
            'code': 'en',
            'name': gettext('en'),
            'redirect_on_fallback': True,
            'public': True,
            'hide_untranslated': False,
            'code': 'cmn',
            'name': "Mandarin Chinese",
            'redirect_on_fallback': True,
            'public': True,
            'hide_untranslated': False,
            'code': 'yue',
            'name': "Cantonese Chinese",
            'redirect_on_fallback': True,
            'public': True,
            'hide_untranslated': False,
    'default': {
        'redirect_on_fallback': True,
        'public': True,
        'hide_untranslated': False,

updating from templates and python code:

./ makemessages -l en
./ makemessages -l yue
./ makemessages -l cmn

"Plural-Forms: nplurals=2; plural=(n != 1);\n" needs to be part of every .po file for DjangoCMS

./ compilemessages