Post on 16-May-2015
description
- Good Practices
selectively and subjectively..
2013
Justyna Żarna Woman in Django / Python World
@Ustinez
http://solution4future.com
Who?
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?
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)
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..
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)
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
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()
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
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
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
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.
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.
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...
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])
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
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 %}
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?
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
@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
photo by Grzegorz Strzelczyk
Explore..
Thank you :))