Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

David Winterbottom

January 06, 2015
Tweet

More Decks by David Winterbottom

Other Decks in Programming

Transcript

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

    View Slide

  2. 2010

    View Slide

  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!

    View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. +
    =
    +

    View Slide

  8. View Slide

  9. E-commerce!
    • Products, baskets, orders, …
    • Everything can be customised
    • Could be added to your site

    View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. ❌ Tax
    ❌ Payment
    ❌ Shipping
    ❌ Fulfilment

    View Slide

  18. View Slide

  19. Raison d'être
    • Make my life easier
    • Help Tangent be more efficient
    • Build complex, maintainable 

    e-commerce sites

    View Slide


  20. My current platform is too slow
    My current platform won’t
    let me do X


    I hate my current platform!

    View Slide

  21. Sweet spots
    • Marketplaces
    • Start-ups
    • B2B

    View Slide

  22. View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  26. Pluggable strategies
    • Multi-currency, multi-territory shops
    • Pluggable tax calculations
    • Per-customer pricing
    • …

    View Slide

  27. Customisation

    View Slide

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

    View Slide

  29. from django.db.models.loading import get_model
    Replace ANY model

    class Meta:
    abstract = True
    +

    View Slide

  30. from oscar.core.loading import get_class
    OrderCreator = get_class(
    'order.utils', 'OrderCreator')
    Replace any CLASS

    View Slide

  31. ⑂ ✍

    View Slide

  32. URLS
    and
    views

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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()

    View Slide

  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
    ...

    View Slide

  41. Oscar
    • For difficult e-commerce problems
    • Designed for flexibility
    • Anything can be customised
    • Customisation techniques drawn
    from Django itself

    View Slide

  42. Lessons

    View Slide

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

    View Slide

  44. Audit models
    • An imported file
    • A request/response with 3rd party

    View Slide

  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)

    View Slide

  46. View Slide

  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)

    View Slide

  48. View Slide

  49. View Slide

  50. External
    transactions
    Digression:

    View Slide

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

    View Slide

  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

    View Slide

  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")

    View Slide

  54. DATABASES = {
    'default': {
    ...
    'ATOMIC_REQUESTS': True
    }
    }
    db = DATABASES['default'].copy()
    db['ATOMIC_REQUESTS'] = False
    DATABASE['autocommit'] = db

    View Slide

  55. AuditModel.objects.using(‘autocommit’) \
    .create(...)

    View Slide

  56. Monitoring
    • 90% off all DVDs!
    • Application metrics
    • High and low thresholds

    View Slide

  57. Tests?



    Docs?
    Metrics?

    View Slide

  58. $ ./manage.py application_metric $name

    View Slide

  59. Finance

    View Slide

  60. View Slide

  61. View Slide

  62. Large (Django) projects

    View Slide

  63. views.py

    View Slide

  64. Boundaries
    • REST interface?
    • Never…
    • …modify a model field
    • …call save()
    dabapps.com/blog/django-models-and-encapsulation/

    View Slide

  65. View Slide

  66. Pollution
    • Dirty/incomplete data
    • Anti-corruption layer
    • PAYMENT.dll

    View Slide

  67. $ make project

    View Slide

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

    View Slide

  69. View Slide

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

    View Slide

  71. @django_oscar
    @codeinthehole

    View Slide