Kindle Notes & Highlights
Read between
March 20 - March 27, 2021
Patterns compensate for the missing language features: Peter Norvig found that 16 of the 23 patterns in design patterns were invisible or simpler in dynamic languages such as Lisp or Python. For instance, as functions are already objects in Python, it would be unnecessary to create separate classes to implement strategy patterns. Patterns repeat best practices: Many patterns are essentially formalizations of best practices, such as separation of concerns, and could seem redundant. Patterns can lead to over-engineering: Implementing the pattern might be less efficient and excessive compared to
...more
"Innovation is not about saying yes to everything. It's about saying NO to all but the most crucial features." – Steve Jobs
I have saved several doomed projects by spending a few days with the client to carefully listen to their needs and set the right expectations. Armed with nothing but a pencil and paper (or their digital equivalents), the process is incredibly simple, but effective. Here are some of the key points to remember while gathering requirements: Talk directly to the application owners even if they are not technically minded. Make sure you listen to their needs fully and note them. Don't use technical jargon such as models. Keep it simple and use end-user friendly terms such as a user profile. Set the
...more
Break down process flows such as user signup. Any multistep functionality needs to be drawn as boxes connected by arrows. Next, work through the features list in the form of user stories or in any easily readable form. Play an active role in prioritizing the features into high, medium, or low buckets. Be very, very conservative in acceptin...
This highlight has been truncated due to consecutive passage length restrictions.
Never be afraid to add more apps or refactor the existing ones into multiple apps. A typical Django project contains 15-20 apps.
Think about how you can move the complexity from code to data. It is always harder to understand logic in code compared to data. UNIX has used this philosophy very successfully by giving many simple tools that can be piped to perform any kind of manipulation on textual data.
Drawing a class diagram of your models like this is recommended. Class attributes might be missing at this stage, but you can detail them later.
Boxes represent entities, which become models. Connector lines are bi-directional and represent one of the three types of relationships in Django: one-to-one, one-to-many (implemented with Foreign Keys), and many-to-many.
Any other code in the __init__.py file will be run when the package is imported. Hence, it is the ideal place for any package-level initialization code.
No attribute (cell) with multiple values A primary key defined as a single column or a set of columns (composite key)
all non-primary key columns must be dependent on the entire primary key. In the previous table, notice that Origin depends only on the superhero, that is, Name. It doesn't matter which Power we are talking about. So, Origin is not entirely dependent on the composite primary key — Name and Power. Let's extract just the origin information into a separate table called Origin, as shown here:
all non-primary key columns must be directly dependent on the entire primary key and must be independent of each other. Think about the Country column for a moment. Given the Latitude and Longitude, you can easily derive the Country column. Even though the country where a superpower was sighted is dependent on the Name-Power composite primary key, it is only indirectly dependent on them. So, let's separate the location details into a separate countries table as follows:
class Meta: unique_together = ("latitude", "longitude")
Normalize while designing, but denormalize while optimizing.
If you have a complex query spanning several tables, such as a count of superpowers by country, then creating a separate denormalized table might improve performance. Typically, this table will be in a faster in-memory database or a cache. As before, we need to update this denormalized table every time the data in your normalized models changes (or you will have the infamous cache-invalidation problem). Denormalization is surprisingly common in large websites because it is a tradeoff between speed and space. Today, space is cheap, but speed is crucial to user experience.
Limitations of abstract models are as follows: They cannot have a Foreign key or many-to-many field from another model They cannot be instantiated or saved They cannot be directly used in a query since it doesn't have a manager
Mixins ought to be orthogonal and easily composable. Drop in a mixin to the list of base classes and they should work. In this regard, they are more similar in behavior to composition rather than inheritance. Smaller mixins are better. Whenever a mixin becomes large and violates the single responsibility principle, consider refactoring it into smaller classes. Let a mixin do one thing and do it well.
Previously, there was no specific place for initializing the signal code. Typically, they were imported or implemented in models.py (which was unreliable). However, with app-loading refactor in Django 1.7, the application initialization code location is well defined.
def ready(self): from . import signals
the profile admin can be made inline to the default user admin by defining a custom UserAdmin in profiles/admin.py as follows: from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import Profile from django.contrib.auth.models import User class UserProfileInline(admin.StackedInline): model = Profile class NewUserAdmin(UserAdmin): inlines = [UserProfileInline] admin.site.unregister(User) admin.site.register(User, NewUserAdmin)
Problem: Models can get large and unmanageable. Testing and maintenance get harder as a model does more than one thing. Solution: Refactor out a set of related methods into a specialized Service object.
Service objects are plain old Python objects (POPOs) that encapsulate a service or interactions with a system. They are usually kept in a separate file named services.py or utils.py.
In most cases, methods of a service object are stateless, that is, they perform the action solely based on the function arguments without using any class properties. Hence, it is better to explicitly mark them as static methods
Each time we call a property, we are recalculating a function. If it is an expensive calculation, we might want to cache the result. This way, the next time the property is accessed, the cached value is returned: from django.utils.functional import cached_property #... @cached_property def full_name(self):
True to their name (or rather the latter half of their name), QuerySets support a lot of (mathematical) set operations.
# Union >>> User.objects.filter(Q(username__in=["a", "b", "c"]) | Q(username__in=["c", "d"])) [<User: a>, <User: b>, <User: c>, <User: d>] # Intersection >>> User.objects.filter(Q(username__in=["a", "b", "c"]) & Q(username__in=["c", "d"])) [<User: c>] # Difference >>> User.objects.filter(Q(username__in=["a", "b", "c"]) & ~Q(username__in=["c", "d"])) [<User: a>, <User: b>]
the Set analogy is not perfect. QuerySets, unlike mathematical sets, are ordered. So, they are closer to Python's list data structure in that respect.
>>>recent = list(posts)+list(comments) >>>sorted(recent, key=lambda e: e.modified, reverse=True)[:3] [<Post: user: Post1>, <Comment: user: Comment1>, <Post: user: Post0>] Unfortunately, this operation has evaluated both the lazy QuerySet objects. The combined memory usage of the two lists can be overwhelming. Besides, it can be quite slow to convert large QuerySets into lists. A much better solution uses iterators to reduce the memory consumption. Use the itertools.chain method to combine multiple QuerySets as follows: >>> from itertools import chain >>> recent =
...more
url(r'^hello-fn/(?P<name>\w+)/$', views.hello_fn), url(r'^hello-fn/$', views.hello_fn), We are reusing the same view function to support two URL patterns. The first pattern takes a name argument. The second pattern doesn't take
simplified routing syntax introduced in Django 2.0. So you will find the following equivalent mappings in viewschapter/urls.py: # In urls.py path('hello-fn/<str:name>/', views.hello_fn), path('hello-fn/', views.hello_fn),
A well-written mixin imposes very little requirements. It should be flexible to be useful in most situations.
The ability of mixins to combine with other classes is both their biggest advantage and disadvantage. Using the wrong combination can lead to bizarre results. So, before using a mixin, you need to check the source code of the mixin and other classes to ensure that there are no method or context-variable clashes.
It can get quite tricky figuring out the order to list the base classes. Like most things in Django, the normal rules of Python apply. Python's Method Resolution Order (MRO) determines how they should be arranged. In a nutshell, mixins come first and base classes come last. The more specialized the parent class is, the more it moves to the left. In practice, this is the only rule you will need to remember.
if B is mentioned before A in the list of base classes, then B's method gets called and vice versa. Now imagine A is a base class such as CreateView and B is a mixin such as FeedMixin. The mixin is an enhancement over the basic functionality of the base class. Hence, the mixin code should act first and in turn, call the base method if needed.
syntactic sugar
@login_required def simple_view(request): return HttpResponse() The following code is exactly the same as the preceding: def simple_view(request): return HttpResponse() simple_view = login_required(simple_view)
Decorators are less flexible than mixins. However, they are simpler. You can use both decorators and mixins in Django. In fact, many mixins are implemented with decorators.
Staff members in Django are users with the is_staff flag set in the user model. Here you can use a built-in mixin called UserPassesTestMixin, as follows: from django.contrib.auth.mixins import UserPassesTestMixin class SomeStaffView(UserPassesTestMixin, TemplateView): def test_func(self, user): return user.is_staff
It is recommended to give users the least amount of privileges to objects as possible. This is called the Principle of least privilege. As a best practice, make sure you are explicit about which users or groups can perform certain actions on your objects rather than going with default access levels.
Most generic class-based views are derived from ContextMixin. It provides the get_context_data method, which most classes override, to add their own context variables. While overriding this method, as a best practice, you will need to call get_context_data of the superclass first and then add or override your context variables.
Short and meaningful URLs are not only appreciated by users, but also by search engines. URLs that are long and have less relevance to the content adversely affect your site's search engine rankings.
URLs belong to a more general family of identifiers called Uniform Resource Identifiers (URIs). Hence, a URL has the same structure as a URI. A URI is composed of several parts: URI = Scheme + Net Location + Path + Query + Fragment For example, a URI (http://dev.example.com:80/gallery/videos?id=217#comments) can be deconstructed in Python using the urlparse function: >>> from urllib.parse import urlparse >>> urlparse("http://dev.example.com:80/gallery/videos?id=217#comments") ParseResult(scheme='http', netloc='dev.example.com:80', path='/gallery/videos', params='', query='id=217',
...more
In many ways, urls.py is the entry point for your project. It is usually the first file I open when I study a Django project. It is like reading a map before exploring a terrain.
Old (regular expression pattern) New (simplified pattern) # Homepage url(r'^$', IndexView.as_view(), name='home'), # Homepage path('', IndexView.as_view(), name='home'), url(r'^about/$', AboutView.as_view(), name='about'), path('about/', AboutView.as_view(), name='home'), url(r'^hello/(?P<name>\w+)/$', views.hello_fn), path('hello/<str:name>/', views.hello_fn), url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/' '(?P<day>[0-9]+)/(?P<pk>[0-9]+)/$', path('<int:year>/<int:month>/' '<int:day>/<int:pk>/',
str
int
slug