Oscar and the Art of Transactional Django Applications

Oscar and the Art of Transactional Django Applications

Oscar is a Django e-commerce framework designed for flexibility. Based on years of hard-earned experience, it employs a range of techniques that allow developers to customise any part of the core. In this way it can be used to build a diverse array of transactional applications. This talk will examine Oscar and the techniques it employs for deep customisation. Further, common patterns of robust e-commerce applications will be examined.

52d39c7b27386ca98bc016119d95b8b8?s=128

David Winterbottom

January 06, 2015
Tweet

Transcript

  1. oscar and the art of transactional django applications @codeinthehole David

    Winterbottom
  2. 2010

  3. Only special customers can see those products Sales reps can

    pay for multiple orders with one payment Sometimes you only pay tax on half a product!
  4. None
  5. None
  6. None
  7. + = +

  8. None
  9. E-commerce! • Products, baskets, orders, … • Everything can be

    customised • Could be added to your site
  10. None
  11. None
  12. None
  13. None
  14. None
  15. None
  16. None
  17. ❌ Tax ❌ Payment ❌ Shipping ❌ Fulfilment

  18. None
  19. Raison d'être • Make my life easier • Help Tangent

    be more efficient • Build complex, maintainable 
 e-commerce sites
  20. My current platform is too slow My current platform won’t

    let me do X I hate my current platform!
  21. Sweet spots • Marketplaces • Start-ups • B2B

  22. None
  23. product = Product.objects.get( upc=“9780981467306")

  24. >>> product.price Decimal('22.60')

  25. >>> strategy = selector.strategy(request) >>> info = strategy.fetch_for_product(
 product) >>>

    info.price.incl_tax
 Decimal(’22.60')
 >>> info.price.currency
 'GBP' >>> info.availability.message
 "In stock" >>> info.availability.is_available_to_buy
 True
  26. Pluggable strategies • Multi-currency, multi-territory shops • Pluggable tax calculations

    • Per-customer pricing • …
  27. Customisation

  28. AUTH_USER_MODEL = ‘myapp.MyUser’ Replace user model 㱺

  29. from django.db.models.loading import get_model Replace ANY model 㱺 class Meta:

    abstract = True +
  30. from oscar.core.loading import get_class OrderCreator = get_class( 'order.utils', 'OrderCreator') Replace

    any CLASS 㱺
  31. ⑂ ✍

  32. URLS and views

  33. from django.conf.urls import patterns, include from django.contrib import admin admin.autodiscover()

    urlpatterns = patterns('', (r'^admin/', include(admin.site.urls)), )
  34. from django.conf.urls import patterns, include from django.contrib import admin admin.autodiscover()

    urlpatterns = patterns('', (r'^admin/', include(admin.site.urls)), )
  35. class MyModelAdmin(admin.ModelAdmin): def get_urls(self): urls = super(MyModelAdmin, self).get_urls() my_urls =

    patterns('', (r'^my_view/$', self.my_view) ) return my_urls + urls
  36. class MyModelAdmin(admin.ModelAdmin): def get_urls(self): urls = super(MyModelAdmin, self).get_urls() my_urls =

    patterns('', (r'^my_view/$', self.my_view) ) return my_urls + urls
  37. from django.conf.urls import * from oscar.app import application urlpatterns =

    patterns('', url(r'', include(application.urls)) )
  38. from django.conf.urls import * from oscar.app import application urlpatterns =

    patterns('', url(r'', include(application.urls)) )
  39. from oscar.core.application import Application class Shop(Application): catalogue_app = get_class('catalogue.app', 'application')

    ... def get_urls(self): urls = [ url(r'^catalogue/', include(self.catalogue_app.urls)), ... ] return urls application = Shop()
  40. from oscar.apps.checkout import app from . import views class CheckoutApplication(app.CheckoutApplication):

    # Replace a view class payment_details_view = views.PaymentDetailsView def get_urls(self): # Tinker with URLs etc ...
  41. Oscar • For difficult e-commerce problems • Designed for flexibility

    • Anything can be customised • Customisation techniques drawn from Django itself
  42. Lessons

  43. Audit • “Why …?” • Log everything* • YPGNI •

    Audit models!
  44. Audit models • An imported file • A request/response with

    3rd party
  45. from django.db import models class PartnerFeedFile(models.Model): filename = models.CharField(max_length=255) PENDING,

    PROCESSED, FAILED = ( 'Pending', 'Processed', 'Failed') STATUSES = ( (PENDING, PENDING), (PROCESSED, PROCESSED), (FAILED, FAILED), ) status = models.CharField( max_length=64, choices=STATUSES, default=PENDING) num_records = models.PositiveIntegerField() date_start_processing = models.DateTimeField(null=True) date_end_processing = models.DateTimeField(null=True)
  46. None
  47. from django.db import models class RequestResponse(models.Model): ... raw_request = models.TextField(max_length=512)

    raw_response = models.TextField(max_length=512) response_time = models.FloatField() date_created = models.DateTimeField( auto_now_add=True)
  48. None
  49. None
  50. External transactions Digression:

  51. DATABASES = { 'default': { ... 'ATOMIC_REQUESTS': True }, }

  52. from django.db import transaction def external_api_call(request): # 1. Save something

    # 2. Make an API call and save audit # 3. Save something else
  53. from django.db import transaction def external_api_call(request): Foo.objects.create(name="Before") with transaction.atomic(): #

    Make some external API call AuditModel.objects.create(...) Foo.objects.create(name="After")
  54. DATABASES = { 'default': { ... 'ATOMIC_REQUESTS': True } }

    db = DATABASES['default'].copy() db['ATOMIC_REQUESTS'] = False DATABASE['autocommit'] = db
  55. AuditModel.objects.using(‘autocommit’) \ .create(...)

  56. Monitoring • 90% off all DVDs! • Application metrics •

    High and low thresholds
  57. Tests? ✅ ✅ ✅ Docs? Metrics?

  58. $ ./manage.py application_metric $name

  59. Finance

  60. None
  61. None
  62. Large (Django) projects

  63. views.py

  64. Boundaries • REST interface? • Never… • …modify a model

    field • …call save() dabapps.com/blog/django-models-and-encapsulation/
  65. None
  66. Pollution • Dirty/incomplete data • Anti-corruption layer • PAYMENT.dll

  67. $ make project

  68. CI examples github.com/tangentlabs /django-oscar /tangent-django-boilerplate

  69. None
  70. • github/django-oscar/django-oscar • v0.8, v1.0 • APIs • You?

  71. @django_oscar @codeinthehole