Writing a Django e-commerce framework

Writing a Django e-commerce framework

From OSCON 2012. This presentation discusses the reasons for writing an e-commerce framework and details a number of techniques for customising Django apps.

52d39c7b27386ca98bc016119d95b8b8?s=128

David Winterbottom

July 18, 2012
Tweet

Transcript

  1. Writing  a  Django   e-­‐commerce   framework OSCON  2012 Friday,

    20 July 12
  2. Me •David  Winterbottom  /  @codeinthehole •Tangent  Labs,  London •Head  of

     E-­‐commerce  team •Python  hacker •commandlinefu.com,  django-­‐oscar Friday, 20 July 12
  3. Synopsis •Motivation •Oscar  -­‐  what  problem  does  it  solve? •Design

     decisions  -­‐  customisation  techniques •E-­‐commerce  tips/advice Friday, 20 July 12
  4. Motivation Friday, 20 July 12

  5. •5  years  of  e-­‐commerce  projects •PHP  =>  Python  /  Django

    •Lots  of  war  stories Tangent  Labs Friday, 20 July 12
  6. Books Friday, 20 July 12

  7. Bookshop  domain •Millions  of  products •ISBNs,  BIC/BISAC  categories,  authors,  publishers

    •Automated  feed-­‐driven  processing •Catalogue •Stock •Rich  data Friday, 20 July 12
  8. Two  tier  structure •Application  tier  -­‐  serves  HTTP  requests •Processing

     tier: •Download  and  import  feeds •Data  cleaning  and  processing •Periodic  jobs  for  fulfilment Friday, 20 July 12
  9. Variations •New  fulfilment  partners •Stock/availability  logic •Multiple  payment  partners •Split-­‐payment

     sources •Multibuy  offers,  vouchers •eBooks! Friday, 20 July 12
  10. PHP  framework •Home-­‐rolled  web  framework •Customisation  via  include  path  overrides

    •Lots  of  duplication •Hard  to  upgrade Friday, 20 July 12
  11. Nailvarnish Friday, 20 July 12

  12. T-­‐shirts Friday, 20 July 12

  13. Digital  music Friday, 20 July 12

  14. Bar  equipment Friday, 20 July 12

  15. New  requirements •Product  variations  -­‐  colours,  sizes  etc •New  fulfilment

     processes:   •webservices  /  warehouses •Per-­‐customer  prices Friday, 20 July 12
  16. B2B •Sales  reps,  customer  hierarchies •Tax  rules •Managed  budgets •Integration

     with  “enterprise”  partners Friday, 20 July 12
  17. •Original  assumptions  all  broken •Domains  vary  wildly •Lots  of  work-­‐arounds

    Friday, 20 July 12
  18. Oscar Friday, 20 July 12

  19. Requirements •Lean  -­‐  as  few  assumptions  as  possible •Models  domain

      •without  duplication •without  too  much  meta-­‐data Friday, 20 July 12
  20. Friday, 20 July 12

  21. Domain  modelling •Implementation  deeply   connected  to  core  business  

    concepts •“...where  powerful  new   features  unfold  as  corollaries  to   older  features.” Friday, 20 July 12
  22. Friday, 20 July 12

  23. Lots  of  others •Satchmo   •Lightning-­‐Fast-­‐Shop •Satchless •Django-­‐shop •Plata,  Mamona,

     Cartridge,  ... •http://djangopackages.com/grids/g/ecommerce/ Friday, 20 July 12
  24. Design  decisions Friday, 20 July 12

  25. Python •Decimal  support •**kwargs •Mixins Friday, 20 July 12

  26. Friday, 20 July 12

  27. Web  framework •Well-­‐thought  out  structure •Templating,  HTTP,  security,  caching,  ...

    •Class-­‐based  views Friday, 20 July 12
  28. Models •Models  drive  the  design •Capture  domain  logic •Foundation  for

     rest  of  application Friday, 20 July 12
  29. Eco-­‐system •Haystack  (search) •South  (database  migrations) •Celery  (job  queue) •Internal

     libraries Friday, 20 July 12
  30. Customisation Friday, 20 July 12

  31. DEBUG = True settings.py Friday, 20 July 12

  32. AUTH_PROFILE_MODULE = ‘customer.Profile’ settings.py Friday, 20 July 12

  33. Different  approach •Key  idea: •Having  the  same  name/identifier  as  your

     parent   •Subclass  and  override Friday, 20 July 12
  34.  Templates Friday, 20 July 12

  35. Friday, 20 July 12

  36. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ) TEMPLATE_DIRS = ( '/var/www/project/templates/',

    ) Replace Friday, 20 July 12
  37. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ) TEMPLATE_DIRS = ( '/var/www/project/templates/'

    '/path/to/oscar/', ) Override parent  of  Oscar’s   template  directory Friday, 20 July 12
  38. # base.html {% extends 'templates/base.html' %} {% block scripts %}

    <script src="new.js" type="text/javascript"></script> {% endblock %} Override Friday, 20 July 12
  39. # base.html {% extends 'templates/base.html' %} {% block scripts %}

    <script src="new.js" type="text/javascript"></script> {{ block.super }} {% endblock %} Extend Friday, 20 July 12
  40. Recap •Filename  is  the  ID •Use  include-­‐path  trick  to  “subclass”

     parent   •Use  blocks  to  provide  hook  points •...but,  don’t  go  too  far Friday, 20 July 12
  41. Apps Friday, 20 July 12

  42. from django.db.loading import get_model Line = get_model('basket', 'Line') Dynamic  loading

     1 INSTALLED_APPS = ( 'oscar.apps.basket' ) Friday, 20 July 12
  43. from django.db.loading import get_model Line = get_model('basket', 'Line') Dynamic  loading

     1 INSTALLED_APPS = ( 'oscar.apps.basket' ) ‘App  label’ Friday, 20 July 12
  44. INSTALLED_APPS = ( 'oscar', 'oscar.apps.catalogue', 'oscar.apps.basket', 'oscar.apps.checkout', 'oscar.apps.order', ... )

    Friday, 20 July 12
  45. INSTALLED_APPS = ( 'oscar', 'oscar.apps.catalogue', 'myproject.basket', 'oscar.apps.checkout', 'oscar.apps.order', ... )

    Same  app  label  as   parent  app Friday, 20 July 12
  46. # myproject/basket/models.py from django.db import models from oscar.apps.basket.abstract_models import \

    AbstractLine class Line(AbstractLine): cost_centre = models.CharField(max_length=64) from oscar.apps.basket.models import * Friday, 20 July 12
  47. INSTALLED_APPS = ( 'oscar', 'oscar.apps.catalogue', 'oscar.apps.basket', 'oscar.apps.checkout', 'oscar.apps.order', ... )

    templatetags management  commands Friday, 20 July 12
  48. Recap •App  label    is  the  ID •Models  can  overridden

     and  extended Friday, 20 July 12
  49. from oscar.core.loading import get_class OrderCreator = get_class('order.utils', 'OrderCreator') Dynamic  loading

     2 Friday, 20 July 12
  50. # order/utils.py from oscar.apps.order.utils import OrderCreator as \ CoreOrderCreator class

    OrderCreator(CoreOrderCreator): def allocate_stock(self, line): # Override/extend method ... Friday, 20 July 12
  51. Recap •(App  module,  class  name)    is  the  ID •Any

     class  can  be  overridden  or  extended Friday, 20 July 12
  52. Tips  and  advice Friday, 20 July 12

  53. Log  everything ...except  sensitive  customer  details. Friday, 20 July 12

  54. Audit  models Friday, 20 July 12

  55. from django.db import models class NielsenDataFile(models.Model): filepath = models.CharField(max_length=128) PENDING,

    FAILED, PROCESSED = range(3) status = models.IntegerField(default=PENDING) num_valid_records = models.IntegerField() num_invalid_records = models.IntegerField() date_downloaded = models.DateTimeField(null=True) date_processed = models.DateTimeField(null=True) Friday, 20 July 12
  56. •High  and  low  thresholds •It’s  always  your  fault  when  things

     go  wrong Monitor  everything Friday, 20 July 12
  57. Service  layers •Avoid  views  talking  to  your  models  directly •See

     ‘Facade’  design  pattern •Anti-­‐corruption  layers Friday, 20 July 12
  58. Generic  vs  bespoke •“If  you’re  using  a  framework,  you  aren’t

     doing  good   modelling” •Capture  the  domain  correctly   •It’s  ok  to  throw  away  the  framework Friday, 20 July 12
  59. Summary •Django  is  great  for  modelling  complex  domains •Writing  customisable

     django  apps  isn’t  easy •Oscar’s  future:   •NoSQL  -­‐  no  more  EAV Friday, 20 July 12
  60. Image  credits Books: http://www.flickr.com/photos/th3ph17/3091294342/ Nail varnish: http://www.flickr.com/photos/kqedquest/831547339/ T-shirts: http://www.flickr.com/photos/blazerman/177165473/ Friday,

    20 July 12