two-scoops-of-django-3.x
two-scoops-of-django-3.x copied to clipboard
Suggestion: Use Choices class of django_model_helpers instead of standard django choices
Django approach
class Flavors(models.TextChoices):
CHOCOLATE = 'ch', 'Chocolate'
VANILLA = 'vn', 'Vanilla'
STRAWBERRY = 'st', 'Strawberry'
CHUNKY_MUNKY = 'cm', 'Chunky Munky'
flavor = models.CharField(max_length=2, choices=Flavors.choices)
django_model_helpers approach
from model_helpers import Choices
# Suggested be placed separately in constants.py file to reduce chances of circular imports
FLAVORS = Choices({
"chocolate": "ch",
"vanilla": "vn",
"strawberry": "st",
"chunky": {"id": "cm", "display": "Chunky Munky"},
}, order_by="id")
class IceCreamOrder(models.Model):
flavor = models.CharField(max_length=2, choices=FLAVORS())
Usage:
# Filter by flavor
IceCreamOrder.objects.filter(flavor=FLAVORS.chocolate)
# Get display name for certain flavor
In [7]: FLAVORS.get_display_name(FLAVORS.chunky)
Out[7]: 'Chunky Munky'
Why second approach is favourable over first one?
- Works on all versions of Django, not just Django 3
- Flexible: You can add extra information about your choices. For example:
FLAVORS = Choices({
"chocolate": {"id": "ch", "help_text": "Made of coca beans and milk"},
"vanilla": {"id": "vn", "help_text": "The basic ice cream flavor"},
"strawberry": {"id": "st", "help_text": "With artificial strawberry flavor"},
"chunky": {"id": "cm", "display": "Chunky Munky", "help_text": "verry chunky"},
}, order_by="id")
Then you can do
In [12]: FLAVORS.get_value(FLAVORS.chocolate, "help_text")
Out[12]: 'Made of coca beans and milk'
- More useful when exposing your code to an API.
Suppose you want to expose an API for mobile developers to interact with your orders.
Since
Choices
class is basically a dictionary, you can json serialize it - as it is - for mobile developers to know what options are available
In [16]: json.dumps(FLAVORS)
Out[16]: '{"chocolate": {"id": "ch", "help_text": "Made of coca beans and milk", "display": "Chocolate"}, "vanilla": {"id": "vn", "help_text": "The basic ice cream flavor", "display": "Vanilla"}, "strawberry": {"id": "st", "help_text": "With artificial strawberry flavor", "display": "Strawberry"}, "chunky": {"id": "cm", "display": "Chunky Munky", "help_text": "verry chunky"}}'
# or
In [27]: json.dumps(list(FLAVORS.keys()))
Out[27]: '["chocolate", "vanilla", "strawberry", "chunky"]'
Moreover, your API can continue using the "code names" of your choices in API requests/responses. For example:
# GET /?flavor=chocolate
user_flavor = request.GET["flavor"]
try:
order.flavor = FLAVORS[user_flavor]["id"]
except KeyError:
return {"error": "Invalid flavor"}
When returning API data regarding your order, you don't need to show its internal database value
return {
"user": order.user.name
"price": order.price,
"flavor": FLAVOR.get_code_value(order.flavor) # return "chocolate" instead of "ch"
}
Finally django_model_helpers has other useful functions that make your life much easier.
For example:
upload_to
import model_helpers
class Profile(models.model):
name = CharField(max_length=100)
picture = ImageField(upload_to=model_helpers.upload_to)
It will automatically create folder for your files (configurable), will do validations to ensure users don't upload dangerous files like .php or .exe (configurable) with almost no effort at all.
cached_model_property
Similar to django's cached_property except that it doesn't cache the value in the model instance but rather it utilize django's builtin cache feature
example:
class Team(models.Model):
@cached_model_property
def points(self):
# Do complex DB queries
return result
# You can manually override the cached value
@cached_model_property(readonly=False)
def editable_points(self):
# get result
return result
# cached value will only be cached for one second (default is 5 minutes)
@cached_model_property(cache_timeout=1)
def one_second_cache(self):
# get result
return result
URL: https://github.com/rewardz/django_model_helpers/
Installation: pip install django-model-helpers
Looks really exciting! I want to use it!
I have some questions about package and project infrastructure. Please see my quick issue at https://github.com/rewardz/django_model_helpers/issues/7