Django Good Practices


Published on

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.

Published in: Technology

Django Good Practices

  1. 1. 2013 - Good Practicesselective ly and subjectively..
  2. 2. Who? Justyna ŻarnaWoman in Django / Python World @Ustinez
  3. 3. About?1. Models: a) a signal to warm-up, b) migrations, c) lets focus on User.2. Views: a) class-based views.3. Templates: a) customization.3. Tools: a) no one likes queue.., b) extend and explore.
  4. 4. Model layer - the warm up1. Save vs Signals:def save(self, *args, **kwargs): #we already have predefined sponsors types, so we cant add next the same type if self.type in ("Silver", "Gold", "Platinum"): return good usage else: super(Sponsor, self).save(*args, **kwargs)def save(self, *args, **kwargs): self.increment_sponsors_number() bad usage super(Event, self).save(*args, **kwargs)a common mistake: forgetting *args, **kwargs
  5. 5. When an object is saving..1. Emit a pre-signal.2. Pre-process the data (automated modification for "specialbehavior fields"). for example: DateField with attribute: auto_now = True3. Preparation the data for the database. for example: DateField and datetime object is prepared to date string in ISO, but simple data types are ready4. Insert to database.5. Emit a post-signal.
  6. 6. Django is sending us the signal..1. Realization of Observer design pattern.2. Applications, pieces of code get notifications about definedactions.3. List of available signals: a) pre_init, post_init, b) pre_save, post_save, write your own signal c) pre_delete, post_delete, d) m2m_changed, e) request_started, request_finished.4. Listening to signals. def hello_function(Sponsor, @receiver(post_save, sender=Event) **kwargs): def hello_function(sender, **kwargs) print (Hello. Im new Sponsor!) print (Hello! Im new Event!) post_save.connect(hello_function)
  7. 7. Race condition problem1. 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 dependson the relative interval of access to resource.4. Good designed project avoids this problem.Example: Gamification, Scoring System, Optimalization, Statistics,Raports etc etc..
  8. 8. Compile all informationclass 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?)@receiver(post_save, sender=Movie) def add_movie_handler(sender, instance, created, **kwargs): if created: instance.lib.movie_counter += 1, sender=Movie)def add_movie_handler(sender, instance, created, **kwargs): if created: instance.lib.movie_counter = F(movie_counter) + 1
  9. 9. Migrations - South1. Schemamigration: Schema evolution, upgrade database schema, history of changes and possibility to move forward and backward in this history. ./ schemamigration myapp --auto2. Datamigration: Data evolution. ./ datamigration myapp update_title def forwards(self, orm): for lib in Library.objects.all(): lib.movie_counter = 100
  10. 10. Migration - south3. Updating migrations: ./ schemamigration myapp --auto --update or... ./ shell form south.models import MigrationHistory migration = MigrationHistory.objects.get(migration = "example_name") migration.delete()4. Team work - merging migrations: ./ migrate --list ./ schemamigration --empty myapp merge_models5. Migration: ./ migrate5. Fixtures - yaml serialization.
  11. 11. Focus on old UserBefore 1.5class Profile(models.Model): user = models.OneToOneField(User, related_name=profile) city = models.CharField(max_length=255, verbose_name=ucity) street = models.CharField(max_length=255, verbose_name=ustreet) house_number = models.CharField(max_length=255, verbose_name=uhomenr) zip_code = models.CharField(max_length=6, verbose_name=uzip code)class MyUser(User): def city_and_code(self): return %s %s % (, self.zip_code) class Meta: verbose_name = u"User" verbose_name_plural = u"Users" db_table = "account_profile" proxy = True
  12. 12. Focus on new UserDjango 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 identifier REQUIRED_FIELDS = (first_name) # mandatory fields for createsuperusermanagement commandAbstractBaseUser provides only core implementation of Userwith hashed passwords and password reset and only few basicmethod like: is_authenticated(), get_full_name(), is_active() etc.AbstractBaseUser is dedicated simple changes for User, notcreating full profile.
  13. 13. Focus on new User2. Extend User like profile:class MyUser(AbstractUser): city = models.CharField(max_length=255, verbose_name=ucity) street = models.CharField(max_length=255, verbose_name=ustreet) house_number = models.CharField(max_length=255, verbose_name=uhome number) zip_code = models.CharField(max_length=6, verbose_name=uzip code)AbstractUser provides full implementation of the Djangosdefault User and UserAdmin is available.
  14. 14. Class-based viewsFunctional 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))Problems:1. A repetitive constructions...2. Low reusability of the code...3. Negation of the principle of DRY...4. Bad readability, monotonicity...
  15. 15. Class-based viewsNew 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")),)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])
  16. 16. Advantages1. 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.
  17. 17. Templates - custom tags and filtersApproach 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 %}Other really lazy developer write this:@register.filter(name=get_val) reusable snippetdef val(value, key): reusable filter return value[key] if value.get(key) else FalseTEMPLATE:{% if config|get_val:key %} TODO{% endif %}
  18. 18. No one likes queue?Celery is an asynchronous tasks queue.Celery in meantime of request send some async tasks toqueue and do some computation.Celery is working with Kombu and RabbitMQ and variousbackends for example Redis etc..Destination: messaging systems, cron task, calculations etc..
  19. 19. Celery tasksExamples:1. Cron jobs in Python code (no need to configurate or ordercron 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.
  20. 20. In action@taskdef 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},}
  21. 21. by Grzegorz Strzelczyk
  22. 22. Thank you :))