I guess that Cristiano may be right.
Making the query only on the Intermediate table would be enough for this case, because the two columns that we need for the query are on this table, and looks like Django is using a JOIN only to get the "id" of Especialidad.
Here's an example:
GIven the Users...
In [2]: Usuario.objects.all().values_list('pk', flat=True)
Out[2]: [1, 2]
...and the Especialidads...
In [3]: Especialidad.objects.all().values_list('pk', flat=True)
Out[3]: [1, 2]
...and the M2M relations.
In [5]: Usuario.objects.get(pk=1).permisosEspecialidad.all().values_list('pk', flat=True)
Out[5]: [1, 2]
In [6]: Usuario.objects.get(pk=2).permisosEspecialidad.all().values_list('pk', flat=True)
Out[6]: [2]
----
The current Django query:
SELECT "samiBackend_especialidad"."id" FROM "samiBackend_especialidade" INNER JOIN "samiBackend_usuario_permisosEspecialidad" ON ( "samiBackend_especialidad"."id" = "samiBackend_usuario_permisosEspecialidad"."especialidade_id" ) WHERE "samiBackend_usuario_permisosEspecialidad"."usuario_id" = 1;
Results: 1, 2
SELECT "samiBackend_especialidade"."id" FROM "samiBackend_especialidad" INNER JOIN "samiBackend_usuario_permisosEspecialidad" ON ( "samiBackend_especialidad"."id" = "samiBackend_usuario_permisosEspecialidad"."especialidade_id" ) WHERE "samiBackend_usuario_permisosEspecialidad"."usuario_id" = 2;
Results: 2
...and the simpler query (just on the Intermediate table)
SELECT "samiBackend_usuario_permisosEspecialidad"."especialidad_id" FROM "samiBackend_usuario_permisosEspecialidad" WHERE "samiBackend_usuario_permisosEspecialidad"."usuario_id" = 1;
Results: 1, 2
SELECT "samiBackend_usuario_permisosEspecialidad"."especialidad_id" FROM "samiBackend_usuario_permisosEspecialidad" WHERE "samiBackend_usuario_permisosEspecialidad"."usuario_id" = 2;
Results: 2
I may be missing something as well, but I think that this optimization may be apropriated.