• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
EuroDjangoCon Django Patterns
 

EuroDjangoCon Django Patterns

on

  • 5,389 views

by James Tauber and Bryan Rosner

by James Tauber and Bryan Rosner

Statistics

Views

Total Views
5,389
Views on SlideShare
5,330
Embed Views
59

Actions

Likes
18
Downloads
141
Comments
2

4 Embeds 59

http://www.slideshare.net 21
http://lanyrd.com 17
http://127.0.0.1 12
http://kmolcmsimply.tk 9

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

12 of 2 previous next

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • The font is Anivers which is also used in the Eldarion logo.
    Are you sure you want to
    Your message goes here
    Processing…
  • Hey - great font. May I ask what it is?

    (Nice presentation too :-) very helpful)
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    EuroDjangoCon Django Patterns EuroDjangoCon Django Patterns Presentation Transcript

    • Django Patterns James Tauber (and Brian Rosner)
    • Django Patterns this is the start not the end of the matter hope it will form basis for more conversation during rest of conference
    • What do I mean by “patterns”? not the same as best practices not quite the same as snippets sometimes just idioms (not proper “design patterns”) more descriptive than prescriptive
    • Example environment-specific settings try: from local_settings import * except ImportError: pass
    • Example Change Log Model class LogMessage(models.Model): thing = models.ForeignKey(Thing) user = models.ForeignKey(User) timestamp = models.DateTimeField( default=datetime.now) message = models.TextField()
    • Example Change Log Model not a reusable model, more “parameterized snippet” class LogMessage(models.Model): thing = models.ForeignKey(Thing) user = models.ForeignKey(User) timestamp = models.DateTimeField( default=datetime.now) message = models.TextField()
    • Some patterns can be turned in to constructs generic views render_to_response new redirect in Django 1.1 view decorators
    • Django IS Python
    • I hope to give... beginning Djangonauts something to try out intermediate Djangonauts something to think about advanced Djangonauts something to laugh at
    • Concepts views template tags urlconfs middleware models context processors templates management commands forms
    • Model Patterns
    • The “Atom” Model class Genre(models.Model): name = models.CharField( max_length=100, unique=True) def __unicode__(self): return self.name
    • Common Fields slug title description creator sometimes just “user” and creation_timestamp “timestamp” when an event
    • “pseudo foreign key” class Node(models.Model): ... subgraph = models.IntegerField( db_index=True)
    • entity attribute values class GameInformation(models.Model): game = models.ForeignKey(Game) attribute = models.CharField( max_length=50) value = models.CharField( max_length=50)
    • entity attribute values class Attribute(models.Model): node = models.ForeignKey(Node) attribute_type = models.CharField( max_length=20, db_index=True) value = models.CharField(max_length=50, db_index=True) class Meta: unique_together = ( ('node', 'attribute_type', 'value'), ) class Node(models.Model): ... def attribute(self, attr_type): return [v[quot;valuequot;] for v in Attribute.objects.filter(node=self, attribute_type=attr_type).values()]
    • zip choices mood = models.CharField( blank=True, max_length=20, choices=zip(MOODS, MOODS))
    • top-level object creation helper GAME_POINTS = { quot;RATED_GAMEquot;: 1, ... } def earn_game_points(user, reason): points = GAME_POINTS[reason] GamePointAward(user=user, points=points, reason=reason).save() ... earn_game_points(user, quot;RATED_GAMEquot;)
    • Basic “Change Log” class LogMessage(models.Model): thing = models.ForeignKey(Thing) user = models.ForeignKey(User) timestamp = models.DateTimeField( default=datetime.now) message = models.TextField()
    • association models without ManyToManyField class WishListItem(models.Model): user = models.ForeignKey(User, related_name=quot;wishlistquot;) game = models.ForeignKey(Game) timestamp = models.DateTimeField( default=datetime.now) ... user_wishlist_items = user.wishlist_items.all() # will avoid O(n) queries to the Game model: user_wishlist_items = user.wishlist_items.select_related(quot;gamequot;)
    • symmetrical associations “friendships” class Friendship(models.Model): to_user = models.ForeignKey(User, related_name=quot;_unused1quot;) from_user = models.ForeignKey(User, related_name=quot;_unused2_quot;) timestamp = models.DateTimeField(default=datetime.now) objects = FriendshipManager() class Meta: unique_together = ( ('to_user', 'from_user'), )
    • class FriendshipManager(models.Manager): def friends_for_user(self, user): friends = [] for friendship in self.filter(from_user=user) .select_related(depth=1): friends.append({quot;friendquot;: friendship.to_user, quot;friendshipquot;: friendship}) for friendship in self.filter(to_user=user) .select_related(depth=1): friends.append({quot;friendquot;: friendship.from_user, quot;friendshipquot;: friendship}) return friends def are_friends(self, user1, user2): if self.filter(from_user=user1, to_user=user2) .count() > 0: return True if self.filter(from_user=user2, to_user=user1) .count() > 0: return True return False
    • Trees class ForumCategory(models.Model): name = models.CharField( max_length=100) parent = models.ForeignKey('self', null=True, blank=True, related_name=quot;subcategoriesquot;)
    • multi-strata trees class Forum(models.Model): title = models.CharField(max_length=100) description = models.TextField() creation_date = models.DateTimeField(default=datetime.now) # must only have one of these (or neither): parent = models.ForeignKey('self', null=True, blank=True, related_name=quot;subforumsquot;) category = models.ForeignKey(ForumCategory, null=True, blank=True, related_name=quot;forumsquot;)
    • lattice class Node(models.Model): children = models.ManyToManyField('self', symmetrical=False, related_name='parents', blank=True)
    • Denormalization and Calculated Field Patterns
    • username as well as user class Profile(models.Model): user = models.ForeignKey(User, unique=True) username = models.CharField( max_length=50)
    • view counts view_count = models.IntegerField( default=0, editable=False) def inc_views(self): self.view_count += 1 self.save()
    • count based on another model class BlogComment(models.Model): blog_post = models.ForeignKey(BlogPost, related_name=quot;commentsquot;) ... class BlogPost(models.Model): ... commment_count = models.IntegerField(default=0, editable=False) def update_comment_count(self): self.comment_count = self.comments.count() self.save() def blog_comment_save(sender, instance=None, created=False, **kwargs): if instance and created: blog_post = instance.blog_post blog_post.update_comment_count() def blog_comment_delete(sender, instance=None, **kwargs): if instance: blog_post = instance.blog_post blog_post.update_comment_count() post_save.connect(blog_comment_save, sender=BlogComment) post_delete.connect(blog_comment_delete, sender=BlogComment)
    • value based on another model class MediaItem(models.Model): overall_rating = models.DecimalField(max_digits=3, decimal_places=1, default=0, editable=False) def update_rating(self): total_rating = 0 total_count = 0 for rating in self.ratings.all(): total_rating += rating.rating total_count += 1 self.overall_rating = str(1. * total_rating / total_count) self.save() class MediaRating(models.Model): media_item = models.ForeignKey(MediaItem, related_name=quot;ratingsquot;) user = models.ForeignKey(Member) rating = models.IntegerField(default=0) timestamp = models.DateTimeField(default=datetime.now) class Meta: unique_together = (quot;media_itemquot;, quot;userquot;)
    • denormalized foreign key class Forum(models.Model): ... last_reply = models.ForeignKey(quot;ForumReplyquot;, null=True, editable=False)
    • Advice on Calculated Fields make sure they are editable=False where possible make them re-calculable (although doesn’t alway make sense) be VERY careful with admin delete when calculated field is foreign key really really wish there was a way to say “if foreign key object is deleted, just null this field”
    • markup fields class TownHallUpdate(models.Model): content = models.TextField() content_html = models.TextField(editable=False) def save(self, **kwargs): self.content_html = textile.textile(sanitize_html(self.content)) super(TownHallUpdate, self).save(**kwargs)
    • one active at a time class TownHallUpdate(models.Model): active = models.BooleanField(default=False, help_text=quot;There can only be one active update. Marking the checkbox will mark any other update as inactive.quot;) def save(self, **kwargs): if self.active: # check for another active one and mark it inactive try: update = TownHallUpdate.objects.get(active=True) except TownHallUpdate.DoesNotExist: pass else: update.active = False update.save() super(TownHallUpdate, self).save(**kwargs) later in view: townhall_update = TownHallUpdate.objects.get(active=True)
    • Query Patterns
    • within date range .filter( Q(start_date__lt=now) | Q(start_date=None), Q(end_date__gt=now) | Q(end_date=None), )
    • ordering by nullables top_blogs = Blog.objects.filter(overall_rating__isnull=False) .order_by(quot;-overall_ratingquot;)[:3]
    • View Patterns
    • standard object retrieval def community_blog(request, blog_id): blog = get_object_or_404(Blog, id=blog_id)
    • standard render return render_to_response(quot;app/bar.htmlquot;, { quot;fooquot;: foo, }, context_instance=RequestContext(request))
    • most views def community_blog(request, blog_id): blog = get_object_or_404(Blog, id=blog_id) ...extra queries... return render_to_response(quot;app/bar.htmlquot;, { quot;blogquot;: blog, ... }, context_instance=RequestContext(request))
    • passing in template name and form class def create(request, form_class=TribeForm, template_name=quot;tribes/create.htmlquot;):
    • building up context dictionary ctx = {} ... if form.had_valid_code: ctx[quot;valid_codequot;] = True ctx[quot;formquot;] = form else: ctx[quot;valid_codequot;] = False ... if beta_code: valid_code = True ctx[quot;formquot;] = BetaSignupForm(initial={quot;beta_codequot;: code}) else: valid_code = False ctx[quot;valid_codequot;] = valid_code return render_to_response(quot;main/beta_signup.htmlquot;, ctx, context_instance=RequestContext(request))
    • submit to different URL then redirect back @login_required @require_POST def comment_submit(request, article_id): ... handle post ... return HttpResponseRedirect( reverse(article_page, args=[article_id]))
    • lots of form submits if request.POST[quot;actionquot;] == quot;add commentquot;: ... elif request.POST[quot;actionquot;] == quot;delete commentquot;: ... elif request.POST[quot;actionquot;] == quot;rename nodequot;: ... elif request.POST[quot;actionquot;] == quot;new nodequot;: ... elif request.POST[quot;actionquot;] == quot;detach nodequot;: ... elif request.POST[quot;actionquot;] == quot;put nodequot;: ... elif request.POST[quot;actionquot;] == quot;add attributequot;: ... elif request.POST[quot;actionquot;] == quot;delete attributequot;: ... elif request.POST[quot;actionquot;] == quot;promote attributequot;: ... elif request.POST[quot;actionquot;] == quot;demote attributequot;: ...
    • handle next def handle_next(request, default=None): if default is None: default = reverse(quot;homequot;) redirect_to = request.REQUEST.get(quot;nextquot;, default) if quot;://quot; in redirect_to: redirect_to = default return redirect_to @login_required def accept_friendship(request, friend_request_id): redirect_to = handle_next(request) FriendshipRequest.objects.get(pk=friend_request_id) .accept() return HttpResponseRedirect(redirect_to)
    • poor man’s search query = request.GET.get(quot;queryquot;, quot;quot;) if query: search_results = Member.objects.filter( Q(username__icontains=query) | Q(first_name__icontains=query) | Q(last_name__icontains=query) | Q(user__email__icontains=query) ) else: search_results = Member.objects.none() # note quot;quot; is used as query default as it is passed through to template for form
    • conditional adding of filters to a chain games = Game.objects.filter(platform__in=PLATFORMS) query = request.GET.get(quot;queryquot;, quot;quot;) letter = request.GET.get(quot;letterquot;, None) if query: filtered_games = games.filter( Q(title__icontains=query) | Q(description__icontains=query) ) else: filtered_games = games if letter: filtered_games = filtered_games.filter(title__istartswith=letter)
    • can encapsulate that in a manager class AnnouncementManager(models.Manager):   def current(self, exclude=[], site_wide=False, for_members=False):     queryset = self.all()     if site_wide:       queryset = queryset.filter(site_wide=True)     if exclude:       queryset = queryset.exclude(pk__in=exclude)     if not for_members:       queryset = queryset.filter(members_only=False)     queryset = queryset.order_by(quot;-creation_datequot;)     return queryset  
    • get first (1) top_reviews = game.user_reviews.order_by(quot;-timestampquot;) if top_reviews: top_review = top_reviews[0] else: top_review = None
    • get first (2) top_reviews = game.user_reviews.order_by(quot;-timestampquot;)[:1] # rely on template to do .0
    • Form Patterns
    • form handling (1) if request.method == quot;POSTquot;: form = form_class(request.POST) if form.is_valid(): ... else: form = form_class()
    • form handling (2) form = form_class(request.POST or None) if tribe_form.is_valid(): ... # relies on fact request.POST is False if # request.method != quot;POSTquot; # AND that is_valid fails if form is unbound
    • “already taken” pattern def clean_username(self): try: user = User.objects.get( username__iexact = self.cleaned_data[quot;usernamequot;]) except User.DoesNotExist: return self.cleaned_data[quot;usernamequot;] raise forms.ValidationError(quot;This username is already taken. Please choose another.quot;) # note iexact!
    • the user form class UserForm(forms.Form): def __init__(self, *args, **kwargs): self.user = kwargs.pop(quot;userquot;) super(UserForm, self).__init__(*args, **kwargs) # can be used for any additional info # can also pass in to save() but then # can’t use in validation
    • order fields class UserPrivacySettingForm(forms.ModelForm): class Meta: model = UserPrivacySetting fields = ( quot;publicquot;, quot;membersquot;, quot;groupsquot;, quot;friendsquot;) def __init__(self, *args, **kwargs): super(UserPrivacySettingForm, self) .__init__(*args, **kwargs) self.fields.keyOrder = self._meta.fields # in 1.1 fields ordering is now significant
    • overriding a field type class ComposeMessageForm(forms.ModelForm): class Meta: model = Message fields = (quot;to_userquot;, quot;subjectquot;, quot;bodyquot;) def __init__(self, member, *args, **kwargs): super(ComposeMessageForm, self) .__init__(*args, **kwargs) self.fields[quot;to_userquot;] = FriendChoiceField(member, label=uquot;Recipientquot;)
    • URL Patterns
    • serving media if settings.SERVE_MEDIA: urlpatterns += patterns('', (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT} ), ) # personally I prefer to separate SERVE_MEDIA # from DEBUG although former can default to # latter
    • per-app URLs url(r'^community/', include('community.urls')),
    • named urls url(r'^article/(d+)/$', ..., name=quot;articlequot;) {% url article article_id %} # use of {% url %} keeps you DRY # use of named-url makes it easier to # drop in replacement apps
    • URL space ^article/$ or ^articles/$ ^article/(d+)/$ or ^article/(w+)/$ ^article/(d+)/comment/$ ^article/add/$ ...
    • Template Patterns
    • using app name as template folder name myapp/ templates/ myapp/ myapp_template.html template_name = quot;myapp/myapp_template.htmlquot;
    • sections of a site have own base template templates/ base.html section1/ base.html some_page.html
    • types of templates top-level extensions includes
    • lists that may be empty {% if results %} <ul> {% for result in results %} <li>...</li> {% endfor %} </ul> {% else %} <p>No results.</p> {% endif %} # in 1.1 {% empty %} can be used for # non-wrapped cases
    • provide an override AND extension point {% block extra_body_base %} {% urchin %} <script src=quot;{{ MEDIA_URL }}base.jsquot; type=quot;text/javascriptquot;></script> {% block extra_body %}{% endblock %} {% endblock %} # bottom-level template doesn't have to # remember block.super
    • Template Tag Patterns
    • types of template tags generate content modify context
    • basic inclusion tag @register.inclusion_tag(quot;includes/media_item.htmlquot;) def media_item(media_item): return { quot;media_itemquot;: media_item, } # could also just use {% include ... %} and rely on # context (possibly with 'with')
    • slip in MEDIA_URL @register.inclusion_tag(quot;community/ inclusion_tags/member_item.htmlquot;) def member_item(member): return { quot;memberquot;: member, quot;MEDIA_URLquot;: settings.MEDIA_URL, }
    • out-of-band calculations on objects @register.simple_tag def default_avatar(member): if member.gender == quot;guyquot;: filename = quot;avatar/guy_default.jpgquot; else: filename = quot;avatar/girl_default.jpgquot; return filename
    • the context, the whole context and nothing but the context @register.inclusion_tag(quot;includes/ nav.htmlquot;, takes_context=True) def nav(context): return context # is this any different than {% include ... %} ?
    • context processors inbox counts other values calculated off current user that are in header or footer especially if request object is needed don’t have to load and call template tag
    • Settings Patterns
    • make relative file paths absolute from os.path import join, dirname MEDIA_ROOT = join(dirname(__file__), quot;site_mediaquot;) TEMPLATE_DIRS = ( join(dirname(__file__), quot;templatesquot;), )
    • environment-specific settings (1) try: from local_settings import * except ImportError: pass
    • environment-specific settings (2) have all per-environment settings files checked-in to version control with symlink from settings.py
    • Meta Patterns
    • Endo Approach (framework-like)
    • Exo Approach (library-like)
    • Parting Thoughts keep the conversation going during conference going to put these (and more) up on eldarion.com pattern library will grow over time
    • James Tauber jtauber@jtauber.com http://jtauber.com/ twitter: @jtauber Brian Rosner brosner@gmail.com http://oebfare.com/ twitter: @brosner