Qualities of great reusable Django apps
Concept
�Throughout the talk, you'll see lots of red words. Those are links. There is a lot of additional content there.
Concept
Concept
�Every time you see some statement with a little square before it, it's an advice. Consider this a checklist for building great Django apps.
That's the concept of a Django app. But what makes an app great?
Easy to install
Easy to install: Distributing
Easy to install: Distributing
Easy to install: Distributing
install_requires=[� 'django-render-block>=0.5',� 'six>=1',�],
Easy to install: Django app vs Python package
�Let's see in practice…
Easy to install: Django app vs Python package
from templated_email import send_templated_mail��send_templated_mail(� template_name='welcome',� from_email='from@example.com',� recipient_list=['to@example.com'],� context={� user: request.user� },�)
Easy to install: Defaults
Easy to install: Config
Easy to install: Config
urlpatterns = [� url('^notifications/',� include(notifications.urls, namespace='notifications')),�]
Easy to install: Upgrade
Easy to use
Easy to use: Docs
Easy to use: Recognition rather than recall
Easy to use: Recognition rather than recall
my_app/� management/� migrations/� templates/� templatetags/� __init__.py� admin.py� apps.py� context_processors.py� exceptions.py� fields.py
� forms.py� helpers.py� managers.py� middleware.py� models.py� querysets.py� signals.py� urls.py� validators.py� views.py� widgets.py�
Easy to use: Fail-fast
Easy to integrate
Easy to adapt it to your project's needs
Discontinuity of integration
Based on the talk Designing and Evaluating Reusable Components
Unsolved
Solved
Slightly overkill
Way overkill
Unsolved
Way overkill
Discontinuity
Options of integration in purple
Imagine you're building a web app for an online store.�They're asking for a feature to�filter products by exact price
Use case: client wants a filter for price field
Options of integration
Integration Work
Benefit to project
Use case: client wants a filter for price field
Integration Work
Benefit to project
Starting here
Implementing with django-filter
class ProductFilter(django_filters.FilterSet):� class Meta:� model = Product� fields = ['price']
New requirement:�filter products by price,�greater than equal and less than equal
Use case: client wants a filter for price field
Integration Work
Benefit to project
New requirements
Use case: client wants a filter for price field
Integration Work
Benefit to project
New requirements
Implementing a filter with django-filter
class ProductFilter(django_filters.FilterSet):� class Meta:� model = Product� fields = {� 'price': ['lte', 'gte'],� }
New requirement:�filter products by price,�greater than equal and less than equal,�but include approximate prices
Use case: client wants a filter for price field
Integration Work
Benefit to project
New requirements
Implementing a filter with django-filter
class CustomPriceFilter(django_filters.NumberFilter):� # ...� �class ProductFilter(django_filters.FilterSet):� price = CustomPriceFilter()� � class Meta:� model = Product
What if django-filter wasn't so extensible?
Use case: client wants a filter for price field
Discontinuity
Integration Work
Benefit to project
New requirements
Integration Work
Benefit to project
More options of integration means more use cases addressed, i.e., a more reusable API
The most important thing to do to make Django apps reusable is to eliminate integration discontinuities
Thankfully, Django abstractions help a lot to eliminate integration discontinuities
Easy to integrate: Django abstractions
Eliminating integration discontinuities is a matter of properly using Django abstractions and further breaking those abstractions into extensible parts
Easy to integrate: Paginator (using properly)
DRF CursorPagination is now more extensible because it has more methods
Easy to integrate: Classes (going extensible)
From this example, we can get a general advice for classes, not only for paginators:
More methods�==�More options of integration
Easy to integrate: Views (using properly)
class CustomPasswordResetView(PasswordResetView):� template_name = 'custom-auth/forgot_password.html'� email_template_name = 'reset_password'� from_email = settings.DEFAULT_FROM_EMAIL� � def form_valid(self, form):� messages.success(� self.request,� _("An email with a reset link has been sent to your inbox."))� return super().form_valid(form)
Easy to integrate: Views (using properly)
class CustomPasswordResetView(PasswordResetView):� template_name = 'custom-auth/forgot_password.html'� email_template_name = 'reset_password'� from_email = settings.DEFAULT_FROM_EMAIL� � def form_valid(self, form):� messages.success(� self.request,� _("An email with a reset link has been sent to your inbox."))� return super().form_valid(form)
Django attrs/methods
Easy to integrate: Views (using properly)
class CustomPasswordResetView(PasswordResetView):� template_name = 'custom-auth/forgot_password.html'� email_template_name = 'reset_password'� from_email = settings.DEFAULT_FROM_EMAIL� � def form_valid(self, form):� messages.success(� self.request,� _("An email with a reset link has been sent to your inbox."))� return super().form_valid(form)
App attrs/methods
Easy to integrate: Views (going extensible)
class ListView(MultipleObjectTemplateResponseMixin, BaseListView):� # methods…� �class BaseListView(MultipleObjectMixin, View):� # methods…
Easy to integrate: Views (going extensible)
class FilterView(MultipleObjectTemplateResponseMixin, BaseFilterView):� # methods…� �class BaseFilterView(FilterMixin, MultipleObjectMixin, View):� # methods…� �class FilterMixin:� # methods…
More mixins�==�More options of integration
Easy to integrate: Template tags (using properly)
{% load avatar_tags %}��{% avatar user=user size=100 %} <!--- 100x100 avatar -->
<img src="https://www.gravatar.com/avatar/123?s=100"� alt="user" width="100" height="100" />
Easy to integrate: Template tags (going extensible)
@register.simple_tag
def avatar(user, size, **kwargs):� for provider_path in settings.AVATAR_PROVIDERS:� provider = import_string(provider_path)� url = provider.get_avatar_url(user, size)� � return render_to_string('avatar/avatar_tag.html', {� 'url': url,� })
* This code is simplified
Easy to integrate: Template tags (going extensible)
class GravatarAvatarProvider:�� def get_avatar_url(self, user, size):� path = generate_gravatar_path(user)� return urljoin(settings.AVATAR_GRAVATAR_BASE_URL, path)� � �class FacebookAvatarProvider:�� def get_avatar_url(self, user, size):� fb_id = get_facebook_id(user)� return f'https://graph.facebook.com/{fb_id}/picture'
* This code is simplified
django-avatar calls them providers.�Others call them backends, services, etc.�They're all custom extensible abstractions,�custom helpers.
More helpers�==�More options of integration
Easy to integrate: Eliminating discontinuities
Easy to integrate: Much more to do
References
Thanks! Questions?
Slides are available at: bit.ly/djangoapps2017�Please contribute: github.com/vintasoftware/django-apps-checklist
Contact me at twitter.com/flaviojuvenal