feldroy / two-scoops-of-django-3.x

The issue tracker, changelog, and code repository for Two Scoops of Django 3.x
https://www.feldroy.com/products/two-scoops-of-django-3-x
342 stars 50 forks source link

Suggestion: Use Choices class of django_model_helpers instead of standard django choices #36

Open ramast opened 4 years ago

ramast commented 4 years ago

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?

  1. Works on all versions of Django, not just Django 3
  2. 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'
  3. 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
ramast commented 4 years ago

URL: https://github.com/rewardz/django_model_helpers/ Installation: pip install django-model-helpers

pydanny commented 4 years ago

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