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.
1. 2013
- Good Practices
selective ly and subjectively..
2. Who?
Justyna Żarna
Woman in Django / Python World
@Ustinez
http://solution4future.com
3. About?
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.
4. Model layer - the warm up
1. Save vs Signals:
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 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. When an object is saving..
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.
6. Django is sending us the signal..
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, 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. I'm new Sponsor!') print ('Hello! I'm new Event!')
post_save.connect(hello_function)
7. Race condition problem
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..
8. Compile all information
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?')
@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()
9. Migrations - South
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 = 100
lib.save()
11. Focus on old User
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')
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
12. Focus on new User
Django 1.5
1. 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 createsuperuser
management command
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.
13. Focus on new User
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')
AbstractUser provides full implementation of the Django's
default User and UserAdmin is available.
14. Class-based views
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))
Problems:
1. A repetitive constructions...
2. Low reusability of the code...
3. Negation of the principle of DRY...
4. Bad readability, monotonicity...
15. Class-based views
New approach:
from django.conf.urls import patterns, url, include
from django.views.generic import ListView
from myapp.models import Library
urlpatterns = 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. Advantages
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.
17. Templates - custom tags and filters
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 %}
Other really lazy developer write this:
@register.filter(name='get_val') reusable snippet
def val(value, key): reusable filter
return value[key] if value.get(key) else False
TEMPLATE:
{% if config|get_val:key %}
TODO
{% endif %}
18. No one likes queue?
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..
19. Celery tasks
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.
20. In action
@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},
}