Brainstorm's snippets (20/247)


  Homebrew Cheatsheet
homebrew
  Howto annotate a QuerySet with calculated properties and use them in ModelAdmin for filtering and sorting
admin, query
  Enable/Disable buttons based on HTML page status
html
  Understand Group by in Django with SQL
django, orm
  Make sure request.is_ajax() return True when sending a POST request from javascript
post
  Google maps alternatives
map
  How to enable Screen Sharing on Macs via Terminal
mac
  Easy Printing
python, print
  Redirect to an arbitrary URL After Saving in Django Admin
django, admin, redirect
  Check reverse relation for OneToOneField
onttoone
  django-selectable usage (example)
selectable
  Automatically zip a FileField upon saving in Django
filefield, zip
  Cherry-picking a GIT commit from another git repository
git
  Sortable column in ModelAdmin.list_display on a calculated property
admin, query
  CSS: How To Center an Object Exactly In The Center
css
  Git partial cherry-picking
git
  How to float a <label> to the left of an <input>?
css
  Django delete FileField
django
  PUB/SUB in javascript
javascript, pubsub
  pyclean alias
python

  Homebrew Cheatsheet

Homebrew cheatsheet

Commands

brew install git Install a package
brew uninstall git Remove/Uninstall a package
brew upgrade git Upgrade a package
brew unlink git Unlink
brew link git Link
brew switch git 2.5.0 Change versions
brew list --versions git See what versions you have

More package commands

brew info git List versions, caveats, etc
brew cleanup git Remove old versions
brew edit git Edit this formula
brew cat git Print this formula
brew home git Open homepage
brew search git Search for formulas

Global commands

brew update Update brew and cask
brew list List installed
brew outdated What’s due for upgrades?
brew doctor Diagnose brew issues

Brew Cask commands

brew cask install firefox Install the Firefox browser
brew cask list List installed applications

(*) Cask commands are used for interacting with graphical applications.

Credits:

https://devhints.io/homebrew

See also:

Uninstalling homebrew

From https://osxdaily.com/2018/08/12/how-uninstall-homebrew-mac/:

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"

From https://apple.stackexchange.com/questions/82807/how-to-cleanly-remove-homebrew#82808:

cd /tmp
cd `brew --prefix`
rm -rf Cellar
brew prune
rm `git ls-files`
rm -r Library/Homebrew Library/Aliases Library/Formula Library/Contributions
rm -rf .git
rm -rf ~/Library/Caches/Homebrew

Homebrew Multi User Setup

From https://medium.com/@leifhanack/homebrew-multi-user-setup-e10cb5849d59:

  • Create a group brew and add Stan
  • brew doctor (Allen checks if he installed brew correctly)
  • sudo chgrp -R brew $(brew --prefix)/* (Change the group of homebrew installation directory)
  • sudo chmod -R g+w $(brew --prefix)/* (Allow group members to write inside this directory)
  • brew doctor (Stan checks if he can use homebrew)
  • We are done!

  Howto annotate a QuerySet with calculated properties and use them in ModelAdmin for filtering and sorting

Objectives

  • add a calculated property to a Model that can be shown in the admin list_display as a sortable and filterable column
  • have the new property available outside the admin
  • avoid any code duplications

the Manager

This can be achieved in a couple of ways:

  • using a custom manager with a method to annotate the queryset, or
  • delegate this activity to a custom QuerySet

In both cases, we can optionally override get_queryset() in the manager to have the calculated property available everywhere:

  • avantages: the calculated property is transparently added to any queryset; no further action is required
  • disadvantages: the computational overhead occurs even when the calculated properties are not used

In the following example, we opted to:

  • not override models.Manager.get_queryset()
  • have the computation available as a separate with_progess() method upon explicit request
  • use a custom QuerySet, so the new method can be chained with other elaborations in an arbitrary order
import datetime
from django.db import models
from django.db.models import Sum, Case, Q, F, When, Value as V, BooleanField, IntegerField
from django.db.models.functions import TruncDay, Now

class SomeQuerySet(models.QuerySet):

    def with_progress(self):
        return (self
            .annotate(
                _ongoing=Case(
                    When(
                        (Q(start_date=None) | Q(start_date__lte=TruncDay(Now()))) &
                        (Q(end_date=None) | Q(end_date__gte=TruncDay(Now()))) ,
                        then=V('True')
                    ),
                    default=(
                        V(False)
                    ),
                    output_field=BooleanField()
                ),
                _worked_hours=Sum("tasks__hours"),
                _progress=Case(
                    When(
                        hours_budget=0,
                        then=0
                    ),
                    default=(
                        F('_worked_hours') * 100.0 / F('hours_budget')
                    ),
                    output_field=IntegerField()
                ),
            )
        )


class SomeManager(models.Manager):

    def get_queryset(self):
        return SomeQuerySet(
            model=self.model,
            using=self._db,
            hints=self._hints
        )

the Model

In the model, we wrap every calculated value with a specific property, and add admin_order_field attribute to enable sorting.

from django.db import models
from .managers import SomeManager


class SomeModel(models.Model):

    objects = SomeManager()

    ...

    #@property
    def worked_hours(self):
        # Requires SomeModel.objects.with_progress()
        return self._worked_hours
    worked_hours.admin_order_field = '_worked_hours'
    worked_hours = property(worked_hours)

    #@property
    def progress(self):
        # Requires SomeModel.objects.with_progress()
        return self._progress
    progress.admin_order_field = '_progress'
    progress = property(progress)

    #@property
    def ongoing(self):
        # Requires SomeModel.objects.with_progress()
        return self._ongoing
    ongoing.boolean = True
    ongoing.admin_order_field = '_ongoing'
    ongoing = property(ongoing)

the ModelAdmin

In the ModelAdmin, the new properties can be added to list_display to obtain sortable columns in the changelist, and/or to readonly_fields to show them up in the changeview.

Here, we also override get_queryset() to make sure that the with_progress() is called as required.

If we did override SomeManager.get_queryset() instead, this wouldn't be necessary.

from django.contrib import admin
from .models import SomeModel

@admin.register(SomeModel)
class SomeModelAdmin(admin.ModelAdmin):

    list_display = [
        ...
        'ongoing',
        'worked_hours',
        'progress',
        ...
    ]
    readonly_fields = [
        ...
        'ongoing',
        'worked_hours',
        'progress',
        ...
    ]

    def get_queryset(self, request):
        queryset = (super().get_queryset(request)
            .with_progress()
            .select_related(
                ...
            )
        )
        return queryset

Filtering

To add filtering on a calculated field, we need one more step: subclass SimpleListFilter as shown below

class IsOngoingFilter(admin.SimpleListFilter):
    title = 'Ongoing'
    parameter_name = 'dummy'

    def lookups(self, request, model_admin):
        return (
            ('Yes', _('Yes')),
            ('No', _('No')),
        )

    def queryset(self, request, queryset):
        value = self.value()
        if value == 'Yes':
            return queryset.filter(_ongoing=True)
        elif value == 'No':
            return queryset.exclude(_ongoing=True)
        return queryset

then, in SomeModelAdmin:

@admin.register(SomeModel)
class SomeModelAdmin(BaseModelAdmin):

    list_filter = [
        ...
        IsOngoingFilter,
        ...
    ]

  Enable/Disable buttons based on HTML page status

<body class="dirty ..." >

    <style>
        a.button-save, a.button-cancel { opacity: 0.5; pointer-events: none; }
        body.dirty a.button-save, body.dirty a.button-cancel { opacity: 1.0; pointer-events: auto; }
        ...
    </style>

    ...

    <div id="toolbar1" class="toolbar">
        <a href="#" class="button-save btn btn-success">Salva</a>
        <a href="#" class="button-cancel btn btn-default">Annulla</a>
    </div>

To enable buttons:

$('body').addClass('dirty');

To disable buttons:

$('body').removeClass('dirty');

  Understand Group by in Django with SQL

Since I never really managed to think about aggregations in term of ORM rather than SQL, I'm practicing and summarizing here the examples described in the interesting article Understand Group by in Django with SQL by Haki Benita, where QuerySets and SQL are put side by side.

How to Use Aggregate Function

sql:

SELECT
    COUNT(id) as total
FROM
    auth_user;

python:

from django.db.models import Count

queryset = (User.objects
    .aggregate(
        total=Count('id'),
    )
)

The aggregate() function accepts an expression to count.

In this case, we used the name of the primary key column id to count all the rows in the table.

The result of aggregate is a dict, and the name of the argument to aggregate becomes the name of the key:

{"total": 891}

Annotations

Annotates each object in the QuerySet with the provided list of query expressions.

An expression may be

  • a simple value
  • a reference to a field on the model (or any related models)
  • or an aggregate expression (averages, sums, etc.) that has been computed over the objects that are related to the objects in the QuerySet.

Each argument to annotate() is an annotation that will be added to each object in the QuerySet that is returned.

See:

https://docs.djangoproject.com/en/2.0/ref/models/querysets/#annotate

How to Group By

We usually want to apply the aggregation on groups of rows:

sql:

SELECT
    is_active,
    COUNT(id) AS total
FROM
    auth_user
GROUP BY
    is_active

python:

queryset = (User.objects
    .values(
        'is_active',
    )
    .annotate(
        total=Count('id'),
    )
    .order_by()
)

To produce a GROUP BY we use a combination of values and annotate:

- values('is_active'): what to group by
- annotate(total=Count('id')): what to aggregate

The order is important: failing to call values before annotate will not produce aggregate results.

Interaction with default ordering or order_by()

When not requiring a specific ordering, we always add an empty ordering list (.order_by()) to prevent possible interactions from a model’s Meta.ordering

Fields that are mentioned in the order_by() part of a queryset (or which are
used in the default ordering on a model) are used when selecting the output
data, even if they are not otherwise specified in the values() call.

These extra fields are used to group “like” results together and they can make
otherwise identical result rows appear to be separate.

This shows up, particularly, when counting things.


Notes:

- Deprecated since version 2.2
- Starting in Django 3.1, the ordering from a model’s Meta.ordering won’t be
  used in GROUP BY queries, such as .annotate().values()
- Since Django 2.2, these queries issue a deprecation warning indicating to
  add an explicit order_by() to the queryset to silence the warning

See:

  https://docs.djangoproject.com/en/3.0/topics/db/aggregation/#interaction-with-default-ordering-or-order-by

How to Filter and Sort a QuerySet With Group By

You can use filter() ans order_by() anywhere in the query:

sql:

SELECT
    is_active,
    COUNT(id) AS total
FROM
    auth_user
WHERE
    is_staff = True
GROUP BY
    is_active
ORDER BY
    is_active,
    total

python:

queryset = (User.objects
    .values(
        'is_active',
    )
    .filter(
        is_staff=True,
    )
    .annotate(
        total=Count('id'),
    )
    .order_by(
        'is_staff',
        'total',
    )
)

Notice that you can sort by both the GROUP BY key and the aggregate field.

How to Combine Multiple Aggregations

To produce multiple aggregations on the same group, add multiple annotations:

sql:

SELECT
    is_active,
    COUNT(id) AS total,
    MAX(date_joined) AS last_joined
FROM
    auth_user
GROUP BY
    is_active

python:

from django.db.models import Max

queryset = (User.objects
    .values(
        'is_active',
    )
    .annotate(
        total=Count('id'),
        last_joined=Max('date_joined'),
    )
    .order_by()
)

How to Group by Multiple Fields

To group by multiple fields, list these fields in values()

sql:

SELECT
    is_active,
    is_staff,
    COUNT(id) AS total
FROM
    auth_user
GROUP BY
    is_active,
    is_staff

python:

queryset = (User.objects
    .values(
        'is_active',
        'is_staff',
    )
    .annotate(
        total=Count('id'),
    )
    .order_by()
)

  Make sure request.is_ajax() return True when sending a POST request from javascript

Looks like is_ajax just checks the HTTP_X_REQUESTED_WITH (looks like X-Requested-With in HTTP) header.

If it equals XMLHttpRequest, we have an ajax query.

Example:

promise = $.ajax({
    type: 'POST',
    url: url,
    data: data,
    cache: false,
    crossDomain: true,
    dataType: 'json',
    headers: {
        'X-CSRFToken': getCookie('csrftoken'),
        'X-Requested-With': 'XMLHttpRequest'  // make sure request.is_ajax() return True on the server
    }
});

Credits: https://stackoverflow.com/questions/8587693/django-request-is-ajax-returning-false/27240729#27240729

  Google maps alternatives

Buonasera a tutti, con il mio team stiamo provando vari servizi di geocoding (reverse/forward) al di là del classico Google. Tra i migliori che abbiamo trovato vorrei segnalare:

Ad ora il servizio che si è rilevato più preciso tra quelli descritti è Here, molto più preciso anche di Photon e Mapbox combinati insieme (il tutto per lavorare con pacchi di CSV di indirizzi da parsare scritti nel peggiore dei modi). Inoltre la versione free ha un limite di richieste molto ampio. Voi cosa usate? Consigli in merito?

Andrea Stagi

Luca Adamo Andrea io uso HERE Maps in un progetto in produzione con circa 9-11M chiamate mese, rispetto a Google ho perso un po' sul geocoding del POI extra UE (attività commerciali strane magari o relativamente nuove) ma risparmio il 90% in fattura mensile (da 15K USD mese Google spendiamo un decimo). In più se fai routing o integri sistemi di previsioni ETA o guida turn-to-turn hai un sacco di opzioni per gestire mezzi non convenzionali (es. truck, che non può girare se la curva è oltre X gradi) e modelli di costo della route custom (in pratica il peso degli archi del grafo te lo puoi decidere in maniera più fine in funzione del mezzo che hai). Secondo me complessivamente è già meglio delle API Google e ci sto passando altri progetti di dimensione analoga. MapBox provato per "gioco" e per farmi un tile-server mio (ottimo per quello con Openlayer), non come servizi REST per cui non so. L'altro lo scopro adesso :)

Andrea Stagi Andrea Stagi Veramente top! Grazie per la panoramica del servizio, soprattutto lato costi diventa davvero interessante

  How to enable Screen Sharing on Macs via Terminal

sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -configure -access -off -restart -agent -privs -all -allowAccessFor -allUsers

Credits:

https://www.techrepublic.com/article/how-to-enable-screen-sharing-on-macs-via-terminal/

  Easy Printing

row = [100, "android", "ios", "blackberry"]
print(', '.join(str(x) for x in row))
print(*row, sep=', ')

Credits:

https://medium.com/better-programming/python-shortcut-tricks-18d676eb58ce

  Redirect to an arbitrary URL After Saving in Django Admin

Put following change_view-method in your admin.py and now you can make links to admin change page that return you back to where you came from:

class BlogEntryAdmin(admin.ModelAdmin):

    ...

    def change_view(self, request, object_id, form_url='', extra_context=None):
        result = super(BlogEntryAdmin, self).change_view(request, object_id, form_url, extra_context)
        if request.GET.get('return_to', False):
            result['Location'] = request.GET['return_to']
        return result

In your template you'd have something like:

{% if request.user.is_staff %}
    <a href="/admin/blog/blogentry/{{ entry.pk }}/?return_to={{ entry.get_absolute_url }}">Edit in admin</a>
{% endif %}

  Check reverse relation for OneToOneField

If related object does not exist, any direct reference to it produces the exception:

RelatedObjectDoesNotExist: Contatore has no pratica.

To avoid it, explicitly test the existence of the attribute:

class Contatore(BaseModel):

    ...

    def is_istallato(self):
        return hasattr(self, 'pratica') and self.pratica is not None

where:

class Pratica(BaseModel):

    ...

    contatore = models.OneToOneField(Contatore, null=True, blank=True, on_delete=models.SET_NULL)

References:

  django-selectable usage (example)

Include django-selectable to the requirements:

django-selectable==1.2.1

jQuery-UI is needed:

npm install jquery-ui-dist

Add selectable to your INSTALLED_APPS:

INSTALLED_APPS [
    ...,
    'selectable',

Add the urls to your root url patterns:

urlpatterns = [
    ...
    path('selectable/', include('selectable.urls')),

Inclusions in file base.html:

<script src="{% static 'jquery/dist/jquery.min.js' %}" type="text/javascript"></script>
<script src="{% static 'jquery-ui-dist/jquery-ui.min.js' %}" type="text/javascript"></script>

<script src="{% static 'selectable/js/jquery.dj.selectable.js' %}" type="text/javascript"></script>

Define the lookup view:

file lookups.py:

from selectable.base import ModelLookup
from selectable.registry import registry
from backend.models import Contatore


class ContatoreLookup(ModelLookup):
    model = Contatore
    search_fields = ('seriale__icontains', )
    filters = {}

    def get_query(self, request, term):
        self.filters['azienda__codice'] = request.GET.get('azienda', '')
        results = super(ContatoreLookup, self).get_query(request, term)
        return results

    def get_item_label(self, item):
        n = 40
        nome = item.nome
        if len(nome) > n:
            nome = nome[:n] + "..."
        return "%s [%s]" % (item.codice, nome)

    def get_item_value(self, item):
        return item.codice

registry.register(ContatoreLookup)

Use AutoCompleteWidget in the form field:

file forms.py:

from django import forms
from selectable.forms import AutoCompleteWidget
from .lookups import ContatoreLookup


class SelectContatoreForm(forms.Form):

    contatore = forms.CharField(
        label='Contatore',
        widget=AutoCompleteWidget(ContatoreLookup, limit=10),
        required=False,
    )

Usage in a modal

You need to call this after form loading:

bindSelectables();

See:

https://github.com/mlavin/django-selectable/issues/78

Tracing selectable events and advanced filtering

function trace_djevents(element) {
    element.on('djselectablecreate', function(event) { console.log('EVENT: djselectablecreate'); });
    element.on('djselectablesearch', function(event) { console.log('EVENT: djselectablesearch'); });
    element.on('djselectableopen', function(event) { console.log('EVENT: djselectableopen'); });
    element.on('djselectablefocus', function(event) { console.log('EVENT: djselectablefocus'); });
    element.on('djselectableselect', function(event) { console.log('EVENT: djselectableselect'); });
    element.on('djselectableclose', function(event) { console.log('EVENT: djselectableclose'); });
    element.on('djselectablechange', function(event) { console.log('EVENT: djselectablechange'); });
}

function after_form_load() {

    // http://django-selectable.readthedocs.io/en/latest/advanced.html
    bindSelectables();

    var azienda = $('#id_azienda');
    var cantiere = $('#id_cantiere');

    trace_djevents(cantiere);

    cantiere.djselectable('option', 'prepareQuery', function(query) {
        query.azienda = azienda.val();
    });

    cantiere.on('djselectableselect', function(event) {
        var url = sprintf('/api/cantiere/%s/%s/', azienda.val(), cantiere.val());
        $.ajax({
            type: 'GET',
            url: url,
            dataType: 'json',
            data: $(this).serialize(),
            success: function(data) {
                console.log(data.nome);
                cantiere.parent().find('.help-block').text(data.nome);
            }
        });
    });

    azienda.on('change', function(event) {
        console.log('azienda changed');
        cantiere.val(null);
        cantiere.parent().find('.help-block').html('&nbsp;');
    });

    setTimeout(function() { cantiere.focus() }, 500);
}

Sample .ui-autocomplete styling (.scss)

.ui-autocomplete {
    // position: absolute;
    // z-index: 3000 !important;
    //cursor: default;

    background-color: white;
    list-style: none;
    padding: 0 8px;
    font-size: 18px;
    border: 1px solid #ccc;

    .ui-menu-item {
        padding: 4px;
        a {
            color: #666;
        }
        .highlight {
            background-color: yellow;
        }
    }
}

  Automatically zip a FileField upon saving in Django

"""
(c) 2019 Mario Orlandi, Brainstorm S.n.c.

Automatically zip a FileField upon saving in Django

__author__    = "Mario Orlandi"
__copyright__ = "Copyright (c) 2019, Brainstorm S.n.c."
__license__   = "GPL"
"""

import os
import zipfile
from django.core.files.storage import FileSystemStorage


class ZipFileStorage(FileSystemStorage):
    """
    A specialized FileSystemStorage which, upon saving the file,
    transparently compresses it into a zip archive (if not already zipped)

    Sample usage:

        source_data = models.FileField(..., storage=ZipFileStorage())
    """

    def save(self, name, content, max_length=None):

        # Let the base storage class save the file as usual
        name = super().save(name, content, max_length)

        # Check if already zipped
        if not name.lower().endswith('.zip'):

            # If not, create a new zip archive and write the file into it
            path = self.path(name)
            zf = zipfile.ZipFile(path + '.zip', mode='w', compression=zipfile.ZIP_DEFLATED)
            try:
                zf.write(path, os.path.basename(path))
            finally:
                zf.close()

            # Cleanup: the orginal unzipped file can now be removed
            os.remove(path)

            # The new return value will be the name of the zip archive just created
            name += '.zip'

        return name

  Cherry-picking a GIT commit from another git repository

This script applies locally the diff of a commit from another repo; "apply" doesn't commits the patch, so you have to supply a commit message yourself

git --git-dir=../[some_other_repo]/.git show [commit_SHA] | git apply

The following script also commits the patch:

git --git-dir=../[some_other_repo]/.git format-patch -k -1 --stdout [commit_SHA] | git am -3 -k --ignore-whitespace

Explanation:

  • The git format-patch command creates a patch
  • from [some_other_repo]'s commit
  • specified by its [commit_SHA]
  • "-1" for one single commit alone
  • "-k" keeps the subject (don't add [PATCH])
  • This patch is piped to git am, which applies the patch locally
  • "-3" means trying the three-way merge if the patch fails to apply cleanly
  • "-k" keeps the subject (don't strip [PATCH])

Explanation:

  Sortable column in ModelAdmin.list_display on a calculated property

class DeAdmin(admin.ModelAdmin):
    list_display = ("[...]", "s_d", "gd", "na", "de", "fr" )

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        queryset = queryset.annotate(
            _s_d=Case(
                When(fr=True, then='s_d'),
                When(fr=False, then=F('gd') + F('na')),
                default=Value(0),
                output_field=IntegerField(),
            )
        )
        return queryset

    def s_d(self, obj):
        return obj._s_d
    s_d.admin_order_field = '_s_d'

References:

https://stackoverflow.com/questions/58366953/how-to-implement-sorting-in-django-admin-for-calculated-model-properties-without

  CSS: How To Center an Object Exactly In The Center

.centered {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

To center text in a div, set line-height equal to height:

.center {
    height: 400px;
    line-height: 400px; /* same as height! */
}

References:

  Git partial cherry-picking

git cherry-pick -n <commit> # get your patch, but don't commit (-n = --no-commit)
git reset                   # unstage the changes from the cherry-picked commit
git add -p                  # make all your choices (add the changes you do want)
git commit                  # make the commit!

It looks like there's a new way to do this with interactively checking out:

git checkout -p bc66559

References:

https://stackoverflow.com/questions/1526044/partly-cherry-picking-a-commit-with-git#1526093

https://stackoverflow.com/questions/1526044/partly-cherry-picking-a-commit-with-git#17376789

  How to float a <label> to the left of an <input>?

<div class="field">
    <label>Test</label>
    <input type="text">
</div>
.field {
    display: table;
    width: 100%;
}

.field > label,
.field > input {
    display: table-cell;
}

.field > input {
    width: 80%;
}

or

.field {
    display: table;
    width: 100%;
}

.field > label,
.field > input {
    display: table-cell;
}

.field > label {
    text-align: right;
    font-weight: bold;
    padding-right: 8px;
}

.field > label:after {
    content: ':';
}

.field > input {
    width: 80%;
}

Credits:

https://stackoverflow.com/questions/32217349/how-to-float-a-label-to-the-left-of-an-input#32217404

  Django delete FileField

import os
from django.db import models
from django.dispatch import receiver


def remove_file_and_cleanup(filepath):
    """
    Helper to remove a media file;
    also removes the containing folder, if left empty
    """
    folder = os.path.dirname(filepath)
    # remove file
    if os.path.isfile(filepath):
        os.remove(filepath)
    # finally, remove folder if empty
    if os.path.isdir(folder) and len(os.listdir(folder)) <= 0:
        os.rmdir(folder)


@receiver(models.signals.post_delete, sender=MyModel)
def auto_delete_file_on_delete(sender, instance, **kwargs):
    """ Deletes file from filesystem when corresponding `MediaFile` object is deleted.
        Adapted from: http://stackoverflow.com/questions/16041232/django-delete-filefield
    """

    # Collect names of FileFields
    fieldnames = [f.name for f in instance._meta.get_fields() if isinstance(f, models.FileField)]
    for fieldname in fieldnames:
        field = getattr(instance, fieldname)
        if bool(field):
            remove_file_and_cleanup(field.path)


@receiver(models.signals.pre_save, sender=MyModel)
def auto_delete_file_on_change(sender, instance, **kwargs):
    """ Deletes file from filesystem when corresponding object is changed or removed.
        Adapted from: http://stackoverflow.com/questions/16041232/django-delete-filefield
    """

    if not instance.pk or sender.objects.filter(pk=instance.pk).count() <= 0:
        return False

    fieldnames = [f.name for f in instance._meta.get_fields() if isinstance(f, models.FileField)]
    for fieldname in fieldnames:
        try:
            old_field = getattr(sender.objects.get(pk=instance.pk), fieldname)
            old_filepath = old_field.path

            new_field = getattr(instance, fieldname)
            new_filepath = new_field.path if new_field else None

            # if ready to save a new file, delete the old one
            if old_filepath != new_filepath:
                remove_file_and_cleanup(old_filepath)
        except:
            pass

  PUB/SUB in javascript

file publisher.js:

function initialize_datatable(element, url) {

    $.ajax({
        type: 'GET',
        url: url + '?action=initialize',
        dataType: 'json'
    }).done(function(data, textStatus, jqXHR) {

        ...
        var table = ...

        // Notify subscribers
        $('.subscribe-datatable-initialized').trigger(
            'datatableInitialized', [table]
        );

    });

}

file sample_subscriber.html:

<div class="panel panel-danger subscribe-datatable-initialized" id="stale-acquisition-panel" style="display: none;">

    <p>This panel is to be shown only after datatable has been initialized</p>

</div>

<script language="javascript">

    $(document).ready(function() {
        $('#stale-acquisition-panel').on('datatableInitialized', function(e, eventInfo) {
            $(e.target).show();
        });
    });

</script>

  pyclean alias

alias pyclean="find . \( -name \*.pyc -o -name \*.pyo -o -name __pycache__ \) -prune -exec rm -rf {} +"