DEV Community

Cover image for Day 64 of #100DaysOfCode — Python Refresher Part 4 + Introduction to Models in Django
M Saad Ahmad
M Saad Ahmad

Posted on

Day 64 of #100DaysOfCode — Python Refresher Part 4 + Introduction to Models in Django

Yesterday I covered functions in depth: *args, **kwargs, decorators, lambdas. Today I moved into OOP in Python. I already knew OOP from C++, and I know how classes work in JavaScript too, so for today, Day 64, was mostly about learning the Python way of doing things. At the end, I also took a first look at Django Models.


OOP in Python

Classes and Objects

A class is a blueprint. An object is an instance of that blueprint.

class Car:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def describe(self):
        return f"{self.brand} {self.model}"

my_car = Car("Toyota", "Corolla")
print(my_car.describe())  # Toyota Corolla
Enter fullscreen mode Exit fullscreen mode

__init__ is the constructor. It runs automatically when you create an object. self is the reference to the current instance, similar to this in JavaScript and C++.


Instance vs Class Attributes

Instance attributes are unique to each object. Class attributes are shared across all instances.

class Employee:
    company = "TechCorp"  # class attribute

    def __init__(self, name):
        self.name = name  # instance attribute

e1 = Employee("Haris")
e2 = Employee("Ali")

print(e1.company)  # TechCorp
print(e2.company)  # TechCorp
print(e1.name)     # Haris
print(e2.name)     # Ali
Enter fullscreen mode Exit fullscreen mode

Instance Methods, Class Methods, and Static Methods

class Circle:
    pi = 3.14159

    def __init__(self, radius):
        self.radius = radius

    def area(self):  # instance method
        return Circle.pi * self.radius ** 2

    @classmethod
    def from_diameter(cls, diameter):  # class method
        return cls(diameter / 2)

    @staticmethod
    def is_valid_radius(r):  # static method
        return r > 0

c = Circle(5)
print(c.area())                        # 78.53975
c2 = Circle.from_diameter(10)
print(c2.radius)                       # 5.0
print(Circle.is_valid_radius(-1))      # False
Enter fullscreen mode Exit fullscreen mode
  • Instance method — works with instance data, takes self
  • Class method — works with class itself, takes cls, decorated with @classmethod
  • Static method — doesn't need instance or class, decorated with @staticmethod

Inheritance

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "Some sound"

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

d = Dog("Bruno")
c = Cat("Whiskers")
print(d.speak())  # Bruno says Woof!
print(c.speak())  # Whiskers says Meow!
Enter fullscreen mode Exit fullscreen mode

super() lets you call the parent class constructor or methods:

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed
Enter fullscreen mode Exit fullscreen mode

Encapsulation

Python doesn't have strict private or public keywords like C++, but uses naming conventions:

class BankAccount:
    def __init__(self, balance):
        self._balance = balance    # protected — convention, not enforced
        self.__pin = 1234          # private — name mangled by Python

    def get_balance(self):
        return self._balance
Enter fullscreen mode Exit fullscreen mode
  • _name — protected by convention, accessible but signals "don't touch"
  • __name — Python mangles it to _ClassName__name, harder to access from outside

Polymorphism

Same interface, different behavior depending on the object.

animals = [Dog("Bruno"), Cat("Whiskers")]

for animal in animals:
    print(animal.speak())
# Bruno says Woof!
# Whiskers says Meow!
Enter fullscreen mode Exit fullscreen mode

Dunder (Magic) Methods

These let you define how your objects behave with Python's built-in operations.

class Book:
    def __init__(self, title, pages):
        self.title = title
        self.pages = pages

    def __str__(self):
        return f"{self.title} ({self.pages} pages)"

    def __len__(self):
        return self.pages

    def __eq__(self, other):
        return self.pages == other.pages

b1 = Book("Clean Code", 431)
b2 = Book("The Pragmatic Programmer", 431)

print(str(b1))    # Clean Code (431 pages)
print(len(b1))    # 431
print(b1 == b2)   # True
Enter fullscreen mode Exit fullscreen mode

Common ones worth knowing:

  • __str__ — called by print() and str()
  • __len__ — called by len()
  • __eq__ — called by ==
  • __repr__ — official string representation, used in debugging

Abstract Classes

When you want to define a blueprint that forces subclasses to implement certain methods:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

r = Rectangle(4, 5)
print(r.area())  # 20
Enter fullscreen mode Exit fullscreen mode

You can't instantiate Shape directly; it is purely a contract for subclasses.


Introduction to Models in Django

Now that OOP is clear, Django Models will make a lot more sense because a Django Model is literally just a Python class.

What is a Model?

A model in Django represents a database table. Each attribute of the class maps to a column in that table. Django handles all the SQL for you.

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title
Enter fullscreen mode Exit fullscreen mode

This single class tells Django to create a post table with columns: title, content, and created_at.


Common Field Types

Field Use
CharField Short text with a max length
TextField Long text, no max length
IntegerField Integer numbers
BooleanField True or False
DateTimeField Date and time
ForeignKey Relationship to another model

Migrations

After defining your model, you don't write SQL. You run two commands:

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

makemigrations generates the migration file based on your model changes. migrate applies those changes to the actual database.


The __str__ Method

Notice the __str__ method in the model above. This is why OOP matters in Django — it controls how your object is displayed in the Django admin panel and shell.

def __str__(self):
    return self.title
Enter fullscreen mode Exit fullscreen mode

Without this, Django just shows something like Post object (1) which tells you nothing.


Wrapping Up

OOP clicked better today now that I'm seeing how directly it connects to Django. Models are just classes, fields are just attributes, and methods like __str__ actually serve a real purpose in the framework.

Thanks for reading. Feel free to share your thoughts!

Top comments (0)