Skip to content

Commit

Permalink
Basic Django interface
Browse files Browse the repository at this point in the history
- Add supporting functions
- Add basic User model
  • Loading branch information
ajparsons committed Dec 16, 2024
1 parent dc78135 commit 7aa86ec
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 0 deletions.
36 changes: 36 additions & 0 deletions src/twfy_tools/db/django_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
This is a simple minimal setup for using Django ORMs.
Import this when creating models and then the models can be used as normal.
"""

import os

import django
from django.conf import settings

from twfy_tools.common.config import config

# Allow use in notebooks
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

if not settings.configured:
settings.configure(
DEBUG=True,
SECRET_KEY="your-secret-key",
ALLOWED_HOSTS=["*"],
INSTALLED_APPS=[
"twfy_tools",
],
DATABASES={
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": config.TWFY_DB_NAME,
"USER": config.TWFY_DB_USER,
"PASSWORD": config.TWFY_DB_PASS,
"HOST": config.TWFY_DB_HOST,
"PORT": config.TWFY_DB_PORT,
}
},
)

django.setup()
52 changes: 52 additions & 0 deletions src/twfy_tools/db/model_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from typing import Any, Callable, TypeVar

from django.db import models

from typing_extensions import ParamSpec, dataclass_transform

FieldType = TypeVar(
"FieldType",
bound=models.Field,
)
P = ParamSpec("P")


def field(
model_class: Callable[P, FieldType],
null: bool = False,
*args: P.args,
**kwargs: P.kwargs,
) -> Any:
"""
Helper function for basic field creation.
So the type checker doesn't complain about the return type
and you can specify the specify type of the item as a typehint.
"""
if args:
raise ValueError("Positional arguments are not supported")
kwargs["null"] = null
if isinstance(model_class, type) and issubclass(model_class, models.Field):
return model_class(**kwargs)
else:
raise ValueError(f"Invalid model class {model_class}")


@dataclass_transform(kw_only_default=True, field_specifiers=(field,))
class DataclassModelBase(models.base.ModelBase):
def __new__(cls, name: str, bases: tuple[type], dct: dict[str, Any], **kwargs: Any):
"""
Basic metaclass to make class keyword parameters into a Meta class.
"""
if kwargs:
dct["Meta"] = type("Meta", (dct.get("Meta", type),), kwargs)
return super().__new__(cls, name, bases, dct)


class DataclassModel(models.Model, metaclass=DataclassModelBase):
"""
Basic wrapper that adds tidier metaclass config, and dataclass
prompting.
"""


class UnManagedDataclassModel(DataclassModel, managed=False): ...
90 changes: 90 additions & 0 deletions src/twfy_tools/db/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
This is a simple one file setup for using django's ORM models.
"""

import datetime
from enum import IntEnum
from typing import Optional

from django.db import models

from twfy_tools.db import django_setup as django_setup

from ..common.enum_backport import StrEnum
from .model_helper import UnManagedDataclassModel, field

datetime_min = datetime.datetime(1, 1, 1, 0, 0, 0)


class UserLevels(StrEnum):
VIEWER = "Viewer"
USER = "User"
MODERATOR = "Moderator"
ADMINISTRATOR = "Administrator"
SUPERUSER = "Superuser"


class OptinValues(IntEnum):
OPTIN_SERVICE = 1
OPTIN_STREAM = 2
OPTIN_ORG = 4


class User(UnManagedDataclassModel, db_table="users"):
user_id: Optional[int] = field(models.AutoField, primary_key=True)
firstname: str = field(models.CharField, max_length=255, default="")
lastname: str = field(models.CharField, max_length=255, default="")
email: str = field(models.CharField, max_length=255)
password: str = field(models.CharField, max_length=102, default="")
lastvisit: datetime.datetime = field(models.DateTimeField, default=datetime_min)
registrationtime: datetime.datetime = field(
models.DateTimeField, default=datetime_min
)
registrationip: str = field(models.CharField, max_length=20, blank=True, null=True)
status: UserLevels = field(
models.CharField,
max_length=13,
blank=True,
null=True,
default=UserLevels.VIEWER,
)
emailpublic: int = field(models.IntegerField, default=0)
optin: int = field(models.IntegerField, default=0)
deleted: int = field(models.IntegerField, default=0)
postcode: str = field(models.CharField, max_length=10, blank=True, null=True)
registrationtoken: str = field(models.CharField, max_length=24, default="")
confirmed: int = field(models.IntegerField, default=0)
url: str = field(models.CharField, max_length=255, blank=True, null=True)
api_key: str = field(
models.CharField, unique=True, max_length=24, blank=True, null=True
)
facebook_id: str = field(models.CharField, max_length=24, blank=True, null=True)
facebook_token: str = field(models.CharField, max_length=200, blank=True, null=True)

UserLevels = UserLevels
OptinValues = OptinValues

def __str__(self):
return f"{self.status}: {self.email}"

def get_optin_values(self) -> list[OptinValues]:
"""
Returns a list of OptinValues that match the user's optin value.
"""
matched_values: list[OptinValues] = []
for value in OptinValues:
if self.optin & value:
matched_values.append(value)
return matched_values

def add_optin(self, optin_value: OptinValues):
"""
Add an optin value to the user.
"""
self.optin |= optin_value

def remove_optin(self, optin_value: OptinValues):
"""
Remove an optin value from the user.
"""
self.optin &= ~optin_value

0 comments on commit 7aa86ec

Please sign in to comment.