Django Cheat Sheet

Table of Contents

Debugging

ABUDE

Always Be Using (the) https://github.com/django-extensions/django-extensions|Django%20Extensions library in all of your Dev environments. Also, makes sure that you also install the `Werkzeug` library with it and run the `runserverplus` command instead of the plain-old `runserver` command when using `manage.py`.

I repeat, do the following for every Dev environment:

$ pip install django-extensions
$ pip install Werkzeug

Unit Testing

General

Testing a single app:

$ ./manage.py test foo

Test everything:

$ ./manage.py test

The http://twoscoopspress.org/products/two-scoops-of-django-1-5|Two%20Scoops%20book recommends the `coverage.py` and `django-discover-runner` packages. And apparently the `django-discover-runner` package is already included in version 1.6 of Django by default.

Comparing Querysets

Use the `assertQuerysetEqual` method. Also, you may get an error like this:

Traceback (most recent call last):
  File "/home/vagrant/Dev/Python/sistemadb_project/sistemadb/inventory/tests/test_views.py", line 354, in test_index_queryset_sort_order
    Student.objects.all().order_by('last_name'))
  File "/usr/local/lib/python2.7/dist-packages/django/test/testcases.py", line 855, in assertQuerysetEqual
    return self.assertEqual(list(items), values, msg=msg)
AssertionError: Lists differ: ['<Student: Bo, Bart>', '<Stud... != [<Student: Bo, Bart>, <Student...

First differing element 0:
<Student: Bo, Bart>
Bo, Bart

- ['<Student: Bo, Bart>', '<Student: Julia, Raul>', '<Student: Purl, Balulah>']
?  -                   -  -                      -  -                        -

+ [<Student: Bo, Bart>, <Student: Julia, Raul>, <Student: Purl, Balulah>]

To fix this, use the `assertQuerysetEqual` method like this:

self.assertQuerysetEqual(set1, [repr (r) for r in set1])

django-simple-history

The https://django-simple-history.readthedocs.org/en/latest/usage.html|following%20quick%20start here is a pretty good resource. However, if you are using Django 1.7 then you need a special step. After updating your models, execute the following steps:

$ python ./manage.py makemigrations myapp --settings=myproj.settings.dev
$ python ./manage.py migrate --settings=myproj.settings.dev

You can then skip the `python manage.py populatehistory –auto` step and proceed to the -Integration with Django Admin- section.

django-rest-framework

Security

If you want to add password protection to a serializer view, then simply add the following property:

permission_classes = (permissions.IsAuthenticated,)

This will secure the actual API. Users will still be able to visit the web interface for the API, but they will get error messages whenever they try to view something.

I haven't found a way yet to block the API web interface via a Django configuration file. However, it should be easy to do via the web server in prod. Either way it should be secure using the previous step.

Forms

Pre-populating A Field Value From A Different Model

The title of this one is a bit awkward. What I needed to do was add a field to my form showing a value from a "reverse" relation. This sort of thing isn't added by default.

First, I had to add the field to the form. Here's the diff:

diff --git a/sistemadb/inventory/forms.py b/sistemadb/inventory/forms.py
index 89edcda..e7357f8 100644
--- a/sistemadb/inventory/forms.py
+++ b/sistemadb/inventory/forms.py
@@ -65,6 +65,12 @@ class InstrumentDeleteConfirmationForm(forms.ModelForm):
         fields = "__all__"

 class StudentUpdateForm(forms.ModelForm):
+    instrument = forms.ModelChoiceField(
+        label = "Instrument",
+        required = False,
+        queryset = Instrument.objects.all(),
+    )
+
     def __init__(self, -args, --kwargs):
         super(StudentUpdateForm, self).__init__(-args, --kwargs)
         self.helper = FormHelper()

Next I had to update my CBV. Here's the diff:

diff --git a/sistemadb/inventory/views.py b/sistemadb/inventory/views.py
index f633230..26352a5 100644
--- a/sistemadb/inventory/views.py
+++ b/sistemadb/inventory/views.py
@@ -124,6 +124,17 @@ class StudentDetailView(generic.UpdateView):
     - FIXME Don't hardcode the URL
     success_url = '/inventory/students'
     model = Student
+    context_object_name = 'student'
+
+    def get_initial(self):
+        """Set the initial value of the instrument dropdown"""
+        student = self.get_object()
+        instrument_pkey = None
+
+        if hasattr(student, 'instrument'):
+            instrument_pkey = student.instrument.pk
+
+        return {'instrument': instrument_pkey}

 class StudentViewSet(viewsets.ModelViewSet):
     queryset = Student.objects.all()

The `contextobjectname` addition wasn't strictly necessary, but I figured it would make things a bit saner. All `getinitial` does is create a dictionary that is appended to the `instance` object that is used to populate the form.

Overall, this is pretty neat and concise, but man did it take me for freaking ever to find the right documentation that showed me how to to this :-)

Also, please see this:

python-social-auth

Gotchas

Docker

Bootstrapping A New Project

First we create the project:

# These commands need to be run outside of Emacs
cd ~/Dev/Python
sudo docker run -it --rm --user "$(id -u):$(id -g)" -v \
       "$PWD":/usr/src/app -w /usr/src/app \
       tompurl/django-dev \
       django-admin.py startproject something_cool

…and then we test it first by running the code in the built-in app server:

cd ~/Dev/Python/something_cool
sudo docker run --name sc-daemon -v "$PWD":/usr/src/app \
     -w /usr/src/app -p 8000:8000 -d tompurl/django-dev \
     ash -c "python manage.py runserver 0.0.0.0:8000"

…and now we can test it:

curl http://localhost:8000/ | elinks -dump

Launching A Shell Container

This is the container you can use to run Django shell commands:

# Please run this command outside of Emacs
cd ~/Dev/Python/my_project
sudo docker run --rm --user "$(id -u):$(id -g)" -v "$PWD":/usr/src/app -w /usr/src/app -it tompurl/django-dev ash

Last Updated .