22
- Good Practices selectively and subjectively.. 2013

Django Good Practices

Embed Size (px)

DESCRIPTION

Slides contain selectively and subjectively choosen topics related with development application in Django framework like: class-based views, signals, customizing User model after 1.5 version released, database migration and queuing tasks using Celery and RabbitMQ.

Citation preview

Page 1: Django Good Practices

- Good Practices

selectively and subjectively..

2013

Page 2: Django Good Practices

Justyna Żarna Woman in Django / Python World

@Ustinez

http://solution4future.com

Who?

Page 3: Django Good Practices

1. Models:a) a signal to warm-up,b) migrations,c) let's focus on User.

2. Views:a) class-based views.

3. Templates:a) customization.

3. Tools:a) no one likes queue..,b) extend and explore.

About?

Page 4: Django Good Practices

1. Save vs Signals:

Model layer - the warm up

good usage

bad usage

a common mistake: forgetting *args, **kwargs

def save(self, *args, **kwargs):#we already have predefined sponsor's types, so we can't add next the same type

if self.type in ("Silver", "Gold", "Platinum"): return else: super(Sponsor, self).save(*args, **kwargs)

def save(self, *args, **kwargs):self.increment_sponsors_number()

super(Event, self).save(*args, **kwargs)

Page 5: Django Good Practices

1. Emit a pre-signal.

2. Pre-process the data (automated modification for "special behavior fields").

for example:DateField with attribute: auto_now = True

3. Preparation the data for the database.for example:DateField and datetime object is prepared to date string in ISO, but simple data types are ready

4. Insert to database.

5. Emit a post-signal.

When an object is saving..

Page 6: Django Good Practices

1. Realization of Observer design pattern.2. Applications, pieces of code get notifications about defined actions.3. List of available signals:

a) pre_init, post_init,b) pre_save, post_save,c) pre_delete, post_delete,d) m2m_changed,e) request_started, request_finished.

4. Listening to signals.

Django is sending us the signal..

@receiver(post_save, sender=Event)def hello_function(sender, **kwargs)

print ('Hello! I'm new Event!')

write your own signal

def hello_function(Sponsor, **kwargs):

print ('Hello. I'm new Sponsor!')post_save.connect(hello_function)

Page 7: Django Good Practices

1. Separate process, threads depend on shared resource.

2. When operations are atomic, shared resource is safe.

3. Problem is difficult to debug, non deterministic and depends on the relative interval of access to resource.

4. Good designed project avoids this problem.

Example:Gamification, Scoring System, Optimalization, Statistics,

Raports etc etc..

Race condition problem

Page 8: Django Good Practices

class Movie(models.Model) title = models.CharField(max_length = 250, verbose_name='title')

lib = models.ForeignKey(Library, verbose_name='library')

class Library movie_counter = models.IntegerField(default = 0, 'how many movies?')

Compile all information

@receiver(post_save, sender=Movie) def add_movie_handler(sender, instance, created, **kwargs): if created: instance.lib.movie_counter += 1 instance.lib.save()

@receiver(post_save, sender=Movie) def add_movie_handler(sender, instance, created, **kwargs): if created: instance.lib.movie_counter = F(movie_counter) + 1 instance.lib.save()

Page 9: Django Good Practices

1. Schemamigration:Schema evolution, upgrade database schema, history of changes and possibility to move forward and backward in this history.

./manage.py schemamigration myapp --auto

2. Datamigration:Data evolution.

./manage.py datamigration myapp update_title

def forwards(self, orm):for lib in Library.objects.all():

lib.movie_counter = 100lib.save()

Migrations - South

Page 10: Django Good Practices

3. Updating migrations:./manage.py schemamigration myapp --auto --updateor..../manage.py shellform south.models import MigrationHistorymigration = MigrationHistory.objects.get(migration = "example_name")migration.delete()

4. Team work - merging migrations:./manage.py migrate --list./manage.py schemamigration --empty myapp merge_models

5. Migration:./manage.py migrate

5. Fixtures - yaml serialization.

Migration - south

Page 11: Django Good Practices

Before 1.5

class Profile(models.Model):user = models.OneToOneField(User, related_name='profile')

city = models.CharField(max_length=255, verbose_name=u'city')street = models.CharField(max_length=255, verbose_name=u'street')house_number = models.CharField(max_length=255, verbose_name=u'home

nr') zip_code = models.CharField(max_length=6, verbose_name=u'zip code')

Focus on old User

class MyUser(User):def city_and_code(self):

return '%s %s' % (self.city, self.zip_code)class Meta:

verbose_name = u"User" verbose_name_plural = u"Users" db_table = "account_profile" proxy = True

Page 12: Django Good Practices

Django 1.51. The simplest way to customize User:class MyUser(AbstractBaseUser): uuid = models.CharFieldField(max_length = 10, unique = True, verbose_name = "user uuid")

USERNAME_FIELD = (uuid) # unique identifierREQUIRED_FIELDS = ('first_name') # mandatory fields for createsuperuser

management command

Focus on new User

AbstractBaseUser provides only core implementation of User with hashed passwords and password reset and only few basic method like: is_authenticated(), get_full_name(), is_active() etc.

AbstractBaseUser is dedicated simple changes for User, not creating full profile.

Page 13: Django Good Practices

2. Extend User like profile:class MyUser(AbstractUser): city = models.CharField(max_length=255, verbose_name=u'city')

street = models.CharField(max_length=255, verbose_name=u'street')house_number = models.CharField(max_length=255, verbose_name=u'home number')

zip_code = models.CharField(max_length=6, verbose_name=u'zip code')

Focus on new User

AbstractUser provides full implementation of the Django's default User and UserAdmin is available.

Page 14: Django Good Practices

Functional approach:

def get_big_libs(request):context = {}context['libs'] = lLibrary.objects.filter(movie_counter__gt = 120)

return render_to_response('template_name.html', {'context': context}, context_instance=RequestContext(request))

Class-based views

Problems:1. A repetitive constructions...2. Low reusability of the code...3. Negation of the principle of DRY...4. Bad readability, monotonicity...

Page 15: Django Good Practices

New approach:from django.conf.urls import patterns, url, includefrom django.views.generic import ListViewfrom myapp.models import Libraryurlpatterns = patterns('', (r'^library/$', ListView.as_view(

queryset=Library.objects.filter(movie_counter__gt = 120),context_object_name="libs_list")),

)

Class-based views

Extending example:urlpatterns = patterns('', (r'^library/(?P<counter>\d+)/$', LibraryListView.as_view()),)class LibraryListView(ListView): context_object_name = "libs_list" template_name = "libs.html" def get_queryset(self): return Library.object.filter(movie_counter__gt=self.args[0])

Page 16: Django Good Practices

1. Class with all benefits:a) inheritance.

2. Generalization.

3. DRY.

4. Fast and simple for common usage.

5. Fast prototyping.

6. Easy and clean extending and customization.

Advantages

Page 17: Django Good Practices

Approach lazy developer:def view1(request):

context = {}context['config'] = {'a': [1,2,3], 'b': ['a', 'b', 'c', 'd', 'e', 'f'']}context['key'] = request.GET.get('key')context['key_options_from_config'] = context['config'][context['key']]return render_to_response('template_name.html', {'context': context})

In template: {% if key_options_from_config %} TODO {% endif %}

Templates - custom tags and filters

reusable snippetreusable filter

Other really lazy developer write this:@register.filter(name='get_val')def val(value, key):

return value[key] if value.get(key) else False

TEMPLATE:{% if config|get_val:key %}

TODO {% endif %}

Page 18: Django Good Practices

Celery is an asynchronous tasks queue.

Celery in meantime of request send some async tasks to queue and do some computation.

Celery is working with Kombu and RabbitMQ and various backends for example Redis etc..

Destination: messaging systems, cron task, calculations etc..

No one likes queue?

Page 19: Django Good Practices

Examples:

1. Cron jobs in Python code (no need to configurate or order cron jobs on server or writing command).

2. Throttled tasks.

3. Delayed tasks.

4. Move heavy load jobs to workers on other machines. (application will not suffer on preformance).

5. Chaining tasks.

Celery tasks

Page 20: Django Good Practices

@task def delete_fake_libraries():

for lib in Library.objects.all():if lib.movie_counter == 0 and lib.book_counter == 0:

lib.delete()

CELERYBEAT_SCHEDULE = { 'check_campaign_active': {'task': myapp.tasks.delete_fake_libraries', 'schedule': crontab(minute='59', hour='23'), 'args': None},}

In action

Page 21: Django Good Practices

photo by Grzegorz Strzelczyk

Explore..

Page 22: Django Good Practices

Thank you :))