diff options
| author | Andrew Godwin | 2022-11-05 14:17:27 -0600 | 
|---|---|---|
| committer | Andrew Godwin | 2022-11-05 14:17:27 -0600 | 
| commit | d77dcf62b4005a0f36ef2fa7ba6d3651d2ef38d7 (patch) | |
| tree | dd356a933b8179a22e5da6e938acd96a175ac0d6 /users/models | |
| download | takahe-d77dcf62b4005a0f36ef2fa7ba6d3651d2ef38d7.tar.gz takahe-d77dcf62b4005a0f36ef2fa7ba6d3651d2ef38d7.tar.bz2 takahe-d77dcf62b4005a0f36ef2fa7ba6d3651d2ef38d7.zip  | |
Initial commit (users and statuses)
Diffstat (limited to 'users/models')
| -rw-r--r-- | users/models/__init__.py | 3 | ||||
| -rw-r--r-- | users/models/identity.py | 79 | ||||
| -rw-r--r-- | users/models/user.py | 58 | ||||
| -rw-r--r-- | users/models/user_event.py | 22 | 
4 files changed, 162 insertions, 0 deletions
diff --git a/users/models/__init__.py b/users/models/__init__.py new file mode 100644 index 0000000..7032a81 --- /dev/null +++ b/users/models/__init__.py @@ -0,0 +1,3 @@ +from .identity import Identity  # noqa +from .user import User  # noqa +from .user_event import UserEvent  # noqa diff --git a/users/models/identity.py b/users/models/identity.py new file mode 100644 index 0000000..495b4a4 --- /dev/null +++ b/users/models/identity.py @@ -0,0 +1,79 @@ +import base64 +import uuid +from functools import partial + +import urlman +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from django.conf import settings +from django.db import models +from django.utils import timezone + + +def upload_namer(prefix, instance, filename): +    """ +    Names uploaded images etc. +    """ +    now = timezone.now() +    filename = base64.b32encode(uuid.uuid4().bytes).decode("ascii") +    return f"{prefix}/{now.year}/{now.month}/{now.day}/{filename}" + + +class Identity(models.Model): +    """ +    Represents both local and remote Fediverse identities (actors) +    """ + +    # The handle includes the domain! +    handle = models.CharField(max_length=500, unique=True) +    name = models.CharField(max_length=500, blank=True, null=True) +    bio = models.TextField(blank=True, null=True) + +    profile_image = models.ImageField(upload_to=partial(upload_namer, "profile_images")) +    background_image = models.ImageField( +        upload_to=partial(upload_namer, "background_images") +    ) + +    local = models.BooleanField() +    users = models.ManyToManyField("users.User", related_name="identities") +    private_key = models.TextField(null=True, blank=True) +    public_key = models.TextField(null=True, blank=True) + +    created = models.DateTimeField(auto_now_add=True) +    updated = models.DateTimeField(auto_now=True) +    deleted = models.DateTimeField(null=True, blank=True) + +    @property +    def short_handle(self): +        if self.handle.endswith("@" + settings.DEFAULT_DOMAIN): +            return self.handle.split("@", 1)[0] +        return self.handle + +    @property +    def domain(self): +        return self.handle.split("@", 1)[1] + +    def generate_keypair(self): +        private_key = rsa.generate_private_key( +            public_exponent=65537, +            key_size=2048, +        ) +        self.private_key = private_key.private_bytes( +            encoding=serialization.Encoding.PEM, +            format=serialization.PrivateFormat.PKCS8, +            encryption_algorithm=serialization.NoEncryption(), +        ) +        self.public_key = private_key.public_key().public_bytes( +            encoding=serialization.Encoding.PEM, +            format=serialization.PublicFormat.SubjectPublicKeyInfo, +        ) +        self.save() + +    def __str__(self): +        return self.name or self.handle + +    class urls(urlman.Urls): +        view = "/@{self.short_handle}/" +        actor = "{view}actor/" +        inbox = "{actor}inbox/" +        activate = "{view}activate/" diff --git a/users/models/user.py b/users/models/user.py new file mode 100644 index 0000000..de51380 --- /dev/null +++ b/users/models/user.py @@ -0,0 +1,58 @@ +from typing import List + +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager +from django.db import models + + +class UserManager(BaseUserManager): +    """ +    Custom user manager that understands emails +    """ + +    def create_user(self, email, password=None): +        user = self.create(email=email) +        if password: +            user.set_password(password) +            user.save() +        return user + +    def create_superuser(self, email, password=None): +        user = self.create(email=email, admin=True) +        if password: +            user.set_password(password) +            user.save() +        return user + + +class User(AbstractBaseUser): +    """ +    Custom user model that only needs an email +    """ + +    email = models.EmailField(unique=True) + +    admin = models.BooleanField(default=False) +    moderator = models.BooleanField(default=False) +    banned = models.BooleanField(default=False) +    deleted = models.BooleanField(default=False) + +    created = models.DateTimeField(auto_now_add=True) +    updated = models.DateTimeField(auto_now=True) + +    USERNAME_FIELD = "email" +    EMAIL_FIELD = "email" +    REQUIRED_FIELDS: List[str] = [] + +    objects = UserManager() + +    @property +    def is_active(self): +        return not (self.deleted or self.banned) + +    @property +    def is_superuser(self): +        return self.admin + +    @property +    def is_staff(self): +        return self.admin diff --git a/users/models/user_event.py b/users/models/user_event.py new file mode 100644 index 0000000..858f334 --- /dev/null +++ b/users/models/user_event.py @@ -0,0 +1,22 @@ +from django.db import models + + +class UserEvent(models.Model): +    """ +    Tracks major events that happen to users +    """ + +    class EventType(models.TextChoices): +        created = "created" +        reset_password = "reset_password" +        banned = "banned" + +    user = models.ForeignKey( +        "users.User", +        on_delete=models.CASCADE, +        related_name="events", +    ) + +    date = models.DateTimeField(auto_now_add=True) +    type = models.CharField(max_length=100, choices=EventType.choices) +    data = models.JSONField(blank=True, null=True)  | 
