RizTech Academy logo
RizTech Academy
Models and the ORMLesson 1 of 635 min

Defining models and fields

The model layer is where a Django project is won or lost. Everything downstream — views, forms, the admin, the API — is built on your models, so a clear, correct data model makes the rest of the course easy, and a muddled one makes every later lesson harder. This lesson defines your first real models for Nidaan and explains what Django does with them: how a Python class becomes a database table, and how to choose fields that model your domain honestly.

A model is a Python class that becomes a table

A Django model is a Python class that inherits from models.Model. Each model maps to one database table; each attribute you declare maps to one column. Here is Nidaan's Patient:

# patients/models.py
from django.db import models


class Patient(models.Model):
    name = models.CharField(max_length=100)
    phone = models.CharField(max_length=15)
    city = models.CharField(max_length=60)
    date_of_birth = models.DateField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

From this one class Django will create a patients_patient table with columns for name, phone, city, date_of_birth, created_at — plus an id primary key it adds automatically. You never write CREATE TABLE; you describe the shape in Python and Django generates the SQL (through migrations, the next lesson). This is the ORM's core promise: you work in Python objects, and the database is an implementation detail you rarely touch directly.

Fields are typed, and the type is a decision

Each field class declares what kind of data the column holds, and choosing the right one is real modelling, not boilerplate. The fields you will use most:

Field For Note
CharField(max_length=…) Short text max_length is required — it sizes the column
TextField() Long/unbounded text No length limit; use for notes, descriptions
IntegerField() / DecimalField(…) Numbers Use DecimalField for money, never FloatField
BooleanField() True/false
DateField() / DateTimeField() Dates / timestamps
EmailField() / URLField() Validated text CharField plus validation
ForeignKey(…) A link to another model The next lessons

A deliberate choice above: phone is a CharField, not an IntegerField. Phone numbers are not quantities — they have leading zeros, +91 prefixes, and you never do arithmetic on them. Storing them as integers loses the leading zero and is a classic beginner mistake. Model what the data is, not what it superficially looks like. Likewise, money would be a DecimalField (exact), never a FloatField (which introduces rounding errors you cannot afford in billing).

null and blank: two different things people conflate

Look at date_of_birth = models.DateField(null=True, blank=True). These two options are constantly confused because they sound similar but control different layers:

  • null=True is about the database: the column may store NULL (no value). Without it, the database requires a value.
  • blank=True is about validation/forms: the field may be left empty in a form or the admin. Without it, forms insist on a value.

So null is the database's rule and blank is the form's rule. For an optional date, you want both — the column can be empty and forms allow it empty. One important exception: for text fields (CharField/TextField), the Django convention is to use blank=True without null=True, because an empty text value is stored as the empty string "", not NULL — two ways to represent "empty" in one column is a mess, so text fields avoid NULL. Remember it as: null for the database, blank for forms; and text fields prefer "" over NULL.

auto_now_add, defaults, and computed-at-save fields

created_at = models.DateTimeField(auto_now_add=True) sets the timestamp automatically the moment the row is first created, and never changes it after. Its sibling auto_now=True updates the timestamp on every save (useful for a updated_at). These, and default=…, let the model fill in values so callers do not have to. A created_at/updated_at pair on important models is a small habit that pays off constantly when you later ask "when did this happen?".

__str__: make your objects legible

The __str__ method decides how an object prints — in the admin, in the shell, in error messages, anywhere Django needs to show a model instance as text. Without it, you get the useless Patient object (1); with it, you get the patient's name. Add a meaningful __str__ to every model. It costs one line and makes the admin and your debugging vastly more usable — a Patient shows as "Asha Patil", not "Patient object (7)".

Choices: a fixed set of valid values

When a field can only be one of a few values — an appointment's status, say — model it with choices rather than a free CharField. Django's TextChoices gives you a readable, validated set:

class Appointment(models.Model):
    class Status(models.TextChoices):
        BOOKED = "booked", "Booked"
        DONE = "done", "Done"
        CANCELLED = "cancelled", "Cancelled"

    status = models.CharField(max_length=10, choices=Status.choices, default=Status.BOOKED)

The database stores the short value ("booked"); the admin and forms show the human label ("Booked"); and you refer to them in code as Appointment.Status.BOOKED, not a magic string. This is the same "make illegal states hard to reach" instinct good engineers apply everywhere — the set of valid statuses lives in one place, named.

A note on where models live and stay clean

Models belong in an app's models.py (or a models/ package for large apps). Keep them focused on shape — fields, relationships, and small methods that describe the object (like __str__ or a full_name property). Heavy business logic is a judgement call this course returns to in the architecture module ("fat models, thin views"); for now, a clean set of fields with sensible types and a good __str__ is exactly the right starting point. Get the shape honest and correct, because everything else is built on it.

Check your work

What a model is. A Python class inheriting models.Model; it maps to one table, and each attribute maps to one column. Django generates the SQL — you describe the shape in Python.

Why field type is a real choice. The type sizes and constrains the column; phone is a CharField (not IntegerField — leading zeros, +91), and money is DecimalField (not FloatField — exactness).

null versus blank. null is the database (column may be NULL); blank is validation/forms (may be empty). Optional non-text fields use both; text fields use blank=True alone and store "", not NULL.

What auto_now_add does. Sets a timestamp once, at creation; auto_now updates on every save. A created_at/updated_at pair is a cheap, useful habit.

Why __str__ matters. It decides how instances display in the admin, shell and errors — add a meaningful one to every model or get Patient object (1).

Why use choices. A fixed set of valid values (statuses) modelled with TextChoices stores a short value, shows a human label, and is referred to by name — one authoritative place for the valid set.

Practice

  1. Create the Patient and Appointment models in the patients app. Add a meaningful __str__ to each.
  2. Add a Doctor model with name and specialisation. Give it a __str__ that returns f"Dr {self.name}".
  3. Deliberately store phone as an IntegerField, add a number with a leading zero, and observe the zero vanish. Change it back to CharField and confirm it survives.
  4. Add an optional notes = models.TextField(blank=True) to Patient and confirm the convention (no null=True) by saving an empty value and checking it is "".
  5. Add a status field to Appointment using TextChoices. In the shell, set it with Appointment.Status.DONE rather than the string.
  6. Add created_at/updated_at (auto_now_add/auto_now) to a model, save it twice, and confirm which one changes.

Official documentation

Next: migrations, and how not to get stuck.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship