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=Trueis about the database: the column may storeNULL(no value). Without it, the database requires a value.blank=Trueis 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
- Create the
PatientandAppointmentmodels in thepatientsapp. Add a meaningful__str__to each. - Add a
Doctormodel withnameandspecialisation. Give it a__str__that returnsf"Dr {self.name}". - Deliberately store
phoneas anIntegerField, add a number with a leading zero, and observe the zero vanish. Change it back toCharFieldand confirm it survives. - Add an optional
notes = models.TextField(blank=True)toPatientand confirm the convention (nonull=True) by saving an empty value and checking it is"". - Add a
statusfield toAppointmentusingTextChoices. In the shell, set it withAppointment.Status.DONErather than the string. - Add
created_at/updated_at(auto_now_add/auto_now) to a model, save it twice, and confirm which one changes.
Official documentation
- Django — Models — The full guide to defining models.
- Django — Model field reference — Every field type and its options, including
null/blank. - Django — Enumeration types (TextChoices) — The choices pattern used for status.
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