.
├── app # I know this app name sucks.
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── selectors.py # Keep your code that fetches data from db here.
│ ├── services.py # Keep your code that creates db objects here.
│ ├── tests.py
│ └── views.py
├── project
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
└── manage.py
Last active
November 4, 2021 01:18
-
-
Save rednafi/9076ce59d6241711641938a68dbe68fa to your computer and use it in GitHub Desktop.
Django Init Model for Bro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import uuid as _uuid # Avoids namespace clashing. | |
from functools import lru_cache | |
from django.contrib.auth.models import AbstractUser | |
from django.db import models | |
# Create your models here. | |
class AuditLogModel(models.Model): | |
"""We need to add 'uuid' and 'timestamps' to every model object. | |
However, we don't want to repeat that, DRY. The auditlog model does that. | |
The name of the class is fairly standardized across the industry. | |
Also, we don't want to create an actual model in this case. | |
So let's create an abstract model.""" | |
# Timestamp should always be in UTC. The client code will need to convert | |
# them as necessary. | |
uuid = models.UUIDField(default=_uuid.uuid4) | |
created_at = models.DateTimeField(auto_now_add=True) | |
updated_at = models.DateTimeField(auto_now=True) | |
is_active = models.BooleanField(default=True) | |
class Meta: | |
# It's an abstract model, so a table isn't created. | |
abstract = True | |
# You usually search via uuids in APIs. Let's add an index here. | |
indexes = [models.Index(fields=["uuid"])] | |
class User(AbstractUser, AuditLogModel): # NOTE: Mixin order is significant! | |
"""A generic user object. You want this because you don't want to hardcode | |
your user to any particular object like patients or customers.""" | |
# Usually second unique identifier is always good. Email is a good one. | |
email = models.EmailField(unique=True, null=True, blank=True) | |
# Max length `2*n -1` where `n % 2 == 0`. | |
first_name = models.CharField(max_length=255, null=True, blank=True) | |
middle_name = models.CharField(max_length=255, null=True, blank=True) | |
last_name = models.CharField(max_length=255, null=True, blank=True) | |
# The order of the decorators is important! | |
@property | |
@lru_cache(maxsize=16) | |
def full_name(self): | |
"""Saving full name in the db would be redundant. Also, people rarely | |
change their names. So let's cache them up to 16 hits.""" | |
return f"{self.first_name} {self.last_name}" | |
class Meta: | |
# Let's add some indexes. Primary fields and foreign keys are indexed by | |
# default. So you don't want to go supersonic with indexing. Indexing make | |
# reads go faster but the writes take a hit. Tradeoffs buddy! We live in a twilight world. | |
# Also, Django allows you to add indexes to the fields themselves. But let's | |
# keep the infrastructure concerns separate from the domain code. | |
# You're probably going to search by 'first_name' and 'last_name' the most. | |
# These are good candidates for indexing. The default B-tree index is a good | |
# starting point. | |
indexes = [ | |
models.Index(fields=["first_name"]), | |
models.Index(fields=["last_name"]), | |
] | |
class Patient(AuditLogModel): | |
""" | |
This model inherits 'uuid', 'full_name', 'created_at', 'updated_at' field form the | |
'AuditLogModel'. | |
""" | |
class Genders(models.TextChoices): | |
"""This is an 'enum.Enum' class. New in Django 3+. | |
Using this enum means you won't have to define the choices multiple times. | |
This is a pain to do in earlier Django versions. Let's utilize that. | |
""" | |
MALE = "M", "MALE" | |
FEMALE = "F", "FEMALE" | |
OTHER = "O", "OTHER" | |
# Every patient is also a user. Cascade delete of sensitive data is bad. So | |
# we use 'on_delete=models.SET_NULL'. Also, adding the model as a string allows | |
# us to take the advantage of postponed evaluation. | |
user = models.OneToOneField( | |
"User", on_delete=models.SET_NULL, null=True, blank=True, related_name="patient" | |
) | |
sex = models.CharField(max_length=1, choices=Genders.choices, default=Genders.MALE) | |
dob = models.DateField(verbose_name="Date of Birth", null=True, blank=True) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""You don't want to keep your domain code in the 'models.py' file. | |
'Services' is a common name for a module that creates data in the db.""" | |
import os | |
import django | |
# This is necessary so that django doesn't raise | |
# django.core.exceptions.ImproperlyConfigured in orphan scripts. | |
# It needs to be set before the model import occurs. | |
os.environ["DJANGO_SETTINGS_MODULE"] = "project.settings" | |
django.setup() | |
import logging | |
import string | |
from datetime import date | |
from . import models as app_models # appName_models is a convention! | |
# Services files should have a logger. | |
logging.basicConfig(level=logging.INFO) | |
def create_user( | |
*, | |
first_name: str, | |
middle_name: str, | |
last_name: str, | |
email: str, | |
) -> None: | |
"""A good practice is to keep the services function keyword only. | |
This is a python 3.8+ feature.""" | |
logging.info(f"Creating user {first_name} {last_name}") | |
app_models.User.objects.create( | |
first_name=first_name, | |
middle_name=middle_name, | |
last_name=last_name, | |
email=email, | |
username=email, | |
) | |
logging.info(f"Success!") | |
def create_patient(*, user: app_models.User, sex: str, dob: date) -> None: | |
logging.info(f"Creating patient with user {user.first_name} {user.last_name}") | |
patient = app_models.Patient.objects.create(sex=sex, dob=dob) | |
patient.user = user | |
patient.save() | |
logging.info(f"Success!") | |
def create_users() -> None: | |
"""Bulk create users.""" | |
for first_name, middle_name, last_name in zip( | |
string.ascii_lowercase, | |
string.ascii_lowercase, | |
string.ascii_lowercase, | |
): | |
email = f"{first_name}.{last_name}@gmail.com" | |
print(email) | |
create_user( | |
first_name=first_name, | |
middle_name=middle_name, | |
last_name=last_name, | |
email=email, | |
) | |
def create_patients() -> None: | |
"""Bulk create patients.""" | |
users = app_models.User.objects.filter( | |
first_name__in=string.ascii_lowercase, | |
last_name__in=string.ascii_lowercase, | |
) | |
for user in users: | |
create_patient(user=user, sex="M", dob=date.today()) | |
if __name__ == "__main__": | |
create_users() | |
create_patients() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from datetime import date | |
from django.test import TestCase | |
from . import models as app_models | |
# Create your tests here. | |
class UserTestCase(TestCase): | |
def setUp(self): | |
# Setting up data. | |
self.first_name = "test_user_first_name" | |
self.middle_name = "test_user_middle_name" | |
self.last_name = "test_user_last_name" | |
self.email = f"{self.first_name}.{self.last_name}@gmail.com" | |
self.username = self.email | |
# Create a user. | |
app_models.User.objects.create( | |
first_name=self.first_name, | |
middle_name=self.middle_name, | |
last_name=self.last_name, | |
email=self.email, | |
username=self.email, | |
) | |
def test_crud(self): | |
user = app_models.User.objects.get(username=self.username) | |
self.assertEqual(user.first_name, self.first_name) | |
self.assertEqual(user.middle_name, self.middle_name) | |
self.assertEqual(user.last_name, self.last_name) | |
self.assertEqual(user.email, self.email) | |
self.assertEqual(user.email, self.username) | |
self.assertEqual(user.full_name, f"{self.first_name} {self.last_name}") | |
class PatientTestCase(TestCase): | |
def setUp(self): | |
# Setting up data. | |
# User data. | |
self.first_name = "test_user_first_name" | |
self.middle_name = "test_user_middle_name" | |
self.last_name = "test_user_last_name" | |
self.email = f"{self.first_name}.{self.last_name}@gmail.com" | |
self.username = self.email | |
# Patient data. | |
self.sex = "M" | |
self.dob = date.today() | |
# Create a user. | |
self.user = app_models.User.objects.create( | |
first_name=self.first_name, | |
middle_name=self.middle_name, | |
last_name=self.last_name, | |
email=self.email, | |
username=self.email, | |
) | |
# Create a patient. | |
app_models.Patient.objects.create( | |
user=self.user, | |
sex=self.sex, | |
dob=self.dob, | |
) | |
def test_crud(self): | |
user = app_models.User.objects.get(username=self.username) | |
patient = app_models.Patient.objects.get(user=user) | |
self.assertEqual(patient.user.first_name, self.first_name) | |
self.assertEqual(patient.user.middle_name, self.middle_name) | |
self.assertEqual(patient.user.last_name, self.last_name) | |
self.assertEqual(patient.user.email, self.email) | |
self.assertEqual(patient.user.email, self.username) | |
self.assertEqual(patient.sex, self.sex) | |
self.assertEqual(patient.dob, self.dob) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
User table looks like this.
Patient Table looks like this after running the second script.