Ponytech

Technology for ponies

Aug 01, 2016

Django 1.10 removed migrate list option

Django 1.10 removed the very helpful --list (or -l) option from the manage migrate command. This option was deprecated since 1.8. But don't worry, you can have the same output using:

./manage.py showmigrations

You can also see in which order your migrations will be applied using the --plan option:

./manage.py showmigrations --plan

Jun 12, 2015

Convert a Foreign Key to Many to Many using Django 1.7 buit-in migrations

Django 1.7 comes with a built-in migration system for your data model, replacing the need of a third party module, usually South. This system is pretty good at doing a lot of stuff automatically, but there are some situations where you need to get your hands dirty, especially when there is data migration involved.

This guide will show you, step by step, how to convert a Foreign Key field into a Many to Many, while preserving existing relations.

Initial set up

Let's say you are starting a pizzeria and want your first pizzas to be very simple, they'll have only one topping:

from django.db import models

class Topping(models.Model):
    name = models.CharField(max_length=20)

class Pizza(models.Model):
    name = models.CharField(max_length=20)
    topping = models.ForeignKey(Topping)

Create the initial migration file (assuming our app is called pizza) and run it:

$ ./manage.py makemigrations pizza
$ ./manage.py migrate


Migrations for 'pizza':
  0001_initial.py:
    - Create model Pizza
    - Create model Topping
    - Add field topping to pizza

And let's create a few pizzas to test our example:

$ ./manage.py shell
from pizza.models import Topping, Pizza
for topping_name, pizza_name in [('mozzarella','Margherita'), ('mushrooms', 'Capricciosa'), ('artichoke', 'Vegetariana'), ('pineapple', 'Hawaii')]:
    topping = Topping.objects.create(name=topping_name)
    pizza = Pizza.objects.create(name=pizza_name, topping=topping)

Here are our data:

sqlite> select * from pizza_topping;
1|mozzarella
2|mushrooms
3|artichoke
4|pineapple

sqlite> select * from pizza_pizza;
1|Margherita|1
2|Capricciosa|2
3|Vegetariana|3
4|Hawaii|4

Convert to Many to Many

After making a few pizzas you soon realize only one topping per pizza is not tasty enough. Let's tweak our model and add a field which have many toppings:

class Pizza(models.Model):
    name = models.CharField(max_length=20)
    topping = models.ForeignKey(Topping, related_name='old_topping')
    toppings = models.ManyToManyField(Topping)

We must add a related_name attribute on one of the 2 fields to have the reverse accessor (from Topping to Pizza) to work. We now run an automatic migration to create the intermediary table that will hold the new relation:

$ ./manage.py makemigrations pizza

Migrations for 'pizza':
  0002_auto_20150612_0903.py:
    - Add field toppings to pizza
    - Alter field topping on pizza

Great. If we run migrate we'll now have an empty table pizza_pizza_toppings that is able to store many toppings per pizza. But we want to keep toppings we already have on our pizzas. So let's create a data migration:

$ ./manage.py makemigrations pizza --empty

Migrations for 'pizza':
  0003_auto_20150612_0920.py:

Edit the newly created file pizza/migrations/0003_auto_xxx.py. We'll create a function that for each pizza, copy data from the field topping to the new field toppings:

from django.db import models, migrations

def forward(apps, schema_editor):
    Pizza = apps.get_model("pizza", "Pizza")
    for pizza in Pizza.objects.all():
        pizza.toppings.add(pizza.topping)

class Migration(migrations.Migration):

    dependencies = [
        ('pizza', '0002_auto_20150612_0903'),
    ]

    operations = [
        migrations.RunPython(forward)
]

After running migrate, we can see toppings relationships had been copied to the new table:

sqlite> select * from pizza_pizza_toppings;
1|1|1
2|2|2
3|3|3
4|4|4

Last step is to clean up our model and remove the old topping field:

class Pizza(models.Model):
    name = models.CharField(max_length=20)
    toppings = models.ManyToManyField(Topping)
$ ./manage.py makemigrations pizza

Migrations for 'pizza':
  0004_remove_pizza_topping.py:
    - Remove field topping from pizza

Voila! You can now start making delicious pizza!

Mar 25, 2014

Use HTTP basic authentification to login into Django

Let's imagine you have a view somewhere on your website you want to password protect using your usual django login but you are too lazy to design a form to input your credentials. This makes sense if the view is for your own use and you don't need to have a fancy login page. In such a case the easiest and fastest way to proceed is to use the standard HTTP basic authentification to let your browser asks for your credentials.

It is then easy to get the user / password back in your view and to authenticate yourself into django. Here is the code snippet to do this :

from django.http import HttpResponse
from django.contrib.auth import authenticate
import base64

def my_view(request):
      if 'HTTP_AUTHORIZATION' in request.META:
              auth = request.META['HTTP_AUTHORIZATION'].split()
              if len(auth) == 2:
                      if auth[0].lower() == "basic":
                              username, password = base64.b64decode(auth[1]).split(':', 1)
                              user = authenticate(username=username, password=password)
                              if user is not None and user.is_staff:
                                      # handle your view here
                                      return render_to_response('my_template.html')

      # otherwise ask for authentification
      response = HttpResponse("")
      response.status_code = 401
      response['WWW-Authenticate'] = 'Basic realm="restricted area"'
      return response

If you need to protect more than one view you should wrap this code in a view decorator.

Please not that using HTTP basic authentification your username and password are sent base64 encoded but as it can be easily decoded you should have your website served over https to keep your crendentials secured.

Next → Page 1 of 3