# Simple Django Tip #5

In this post, I plan to continue with some more tips to keep in mind while designing Django models. Just in case you are curious what's Part 1, [here it is](https://hellosambhavi.com/simple-django-tip-4) 😀

And yes, I'm taking the same hypothetical website that sells and delivers ice cream 🍦. Below are the models that I will use to explain the tips:

* Customer
    
* Icecream
    
* Order
    

Let's dive right in!

### Use matching field types

Django models have a lot of field types to choose from, literally, every single field type can be handled appropriately. Per the documentation, a complete of field types [here](https://docs.djangoproject.com/en/4.2/ref/models/fields/#field-types).

Using the correct field type helps in data integrity, readability of code, and it will also improve performance.

Let's look at a few examples.

In the `Order` model, there is a column `OrderedAt` which denotes when a Customer ordered an ice cream. It would be ideal to define this as a `DateTime` column rather than a `Date` column. The reason is, that if the owner of the website wants to analyze the pattern of sales, time would be a crucial aspect.

In the `Customer` model, there is a column `Avatar` which holds the profile picture of a Customer. It would make more sense to define it as an `ImageField` rather than a `FileField.` The reason in this case would be that the `ImageField` inherits the properties of a `FileField` also validates whether the uploaded file is an image. Direct validation of a business rule, is it not?

In the `Ice cream` model, there is a column that points to a link where the corresponding image of each type of ice cream is loaded. In such a case, it would be ideal to define it as a `URLField` rather than a plain `CharField` as the former also validates whether the input is a valid URL.

### Use empty string for String-based fields

When you define a `CharField` and it's not a mandatory one, use `blank=True` and not `null=True`. The reason for this being is, Django's convention is to use an empty string and not null.

A common example that can be quoted here is, in a Customer field, we can add a column like the `Address2` field which will be optional in most cases. We can define such a column like so 👇

```python
class Customer(models.Model):
    first_name = models.CharField(...)
    ...
    address2 = models.CharField("Address 2", max_length=50, blank=True)
```

### Make sure to define `__str__` method

Whenever you define a model, make sure to define a `__str__` method. This helps a lot when we debug. It also comes in very handy with the Django admin site when it displays objects.

Let's look at how we define them 👇

```python
class Customer(models.Model):
    first_name = model.CharField("First name", max_length=50, 
                       verbose_text="How do I address you?")
    last_name = model.CharField("Last name", max_length=50, 
                       verbose_text="Your last name please")
    email_address = model.EmailField("Email", max_length=50,
                       verbose_text="Email address where you can be reached"_
    ....
    created_by = models.ForeignKey(User, on_delete=models.CASCADE,
                       related_by="user_who_created")

    def __str__(self):
       # This is an option. It can also be just email.
       # It is upto the developer's discretion to define
       # depending on uniqueness and how much it would help with
       # debugging and readability
       return self.first_name + " " + self.last_name + " - " + email
```

### Use `select_related` while fetching data

When we fetch data from a table in code, it is good practice to use `select_related` when compared to plain lookups. The reason being, `select_related` returns a `Queryset` that will follow foreign key relationships, thus fetching additional data when we issue a query.

This improves performance although it creates a single complex query. But there will be no further querying as it fetches appropriate additional data.

Let's see an example 👇

```python
class Order(models.Model):
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE, 
                 related_name='customer_who_ordered')
    icecreams = models.ManyToManyField(Icecream, on_delete=models.CASCADE, 
                 related_name='icecreams_ordered')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=20, choices=[
        ('pending', 'Pending'),
        ('processing', 'Processing'),
        ('completed', 'Completed'),
        ('cancelled', 'Cancelled'),
    ])
    total_price = models.DecimalField(max_digits=10, decimal_places=2, \
                  blank=True)

    class Meta:
        ordering = ['-created_at']
        verbose_name = 'Order'
        verbose_name_plural = 'Orders'

    def __str__(self):
        return f"Order {self.id} - {self.status}"
    

# INCORRECT
order_details = Order.objects.get(id=101)
customer_details = order_details.customer

# CORRECT
icecream_details = Icecream.objects.select_related(
                "customer").get(id=101)
```

### Normalize your data unless there is a specific need

In general, for a normal business application, it is good practice to normalize the data. Relationships between two entities should be maintained through Foreign Keys therefore we do not duplicate data. Denormalized database design is common in data warehouses and analytical dashboards.

When a framework like Django is used, the underlying database will most probably be used for CRUD operations. Hence it's always best to normalize the models when we design.

### Complete design of the Icecream model

```python
class Icecream(models.Model):
    flavor_choices = (
        ("butterscotch", "Butterscotch"),
        ("carmel", "Carmel"),
        ("pista", "Pista"),
        ("strawberry", "Strawberry"),
        ("vanilla", "Vanilla")
    )
    flavor = models.CharField("Choose your flavor", choices=flavor_choices,
          default="vanilla")
    description = models.CharField("Describe what the flavor is all about",
         max_length=150, blank=True)
    is_available = models.BooleanField("Are you selling this now?", 
        default=True)
    price = models.DecimalField("Price per 100 gms", 
        max_digits=5, decimal_places=2)
    ingredients = models.TextField("What are the main ingredients?")
    allergens = models.CharField("What allergens are present?",
        max_length=255, blank=True)
    nutritional_info = models.TextField("What is the nutritional
        info?", blank=True)
    is_vegan = models.BooleanField(default=False)
    is_gluten_free = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['flavor']
        verbose_name = 'Ice Cream'
        verbose_name_plural = 'Ice Creams'

    def __str__(self):
        return f"{self.flavor}
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><code>Order</code> model is already defined as part of one of the tips above.</div>
</div>

### Closing thoughts

In this and the [previous post](https://hellosambhavi.com/simple-django-tip-4), we saw a couple of tips to remember while we define models in Django. They bring in a lot of benefits from various aspects.

Hope you found these tips to be useful!
