Files
website-v2/users/models.py
Lacey Williams Henschel f2a0da771e Add signal for saving profile data upon a new GH login
I did skip the tests -- my first tries with `patch` didn't work and I wanted to have this working for the demo, at least locally.
2023-02-16 15:22:06 -08:00

206 lines
6.6 KiB
Python

import logging
import os
import requests
from django.conf import settings
from django.contrib.auth.models import (
AbstractBaseUser,
PermissionsMixin,
BaseUserManager,
)
from django.core.files import File
from django.core.mail import send_mail
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from libraries.github import get_api as get_github_api
from libraries.github import get_user_by_username as get_github_user
logger = logging.getLogger(__name__)
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""
Creates and saves a User with the given username, email and password.
"""
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
logger.info("Creating user with email='%s'", email)
return self._create_user(email, password, **extra_fields)
def create_staffuser(self, email, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", False)
logger.info("Creating staff user with email='%s'", email)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
if extra_fields.get("is_staff") is not True:
raise ValueError("Superuser must have is_staff=True.")
if extra_fields.get("is_superuser") is not True:
raise ValueError("Superuser must have is_superuser=True.")
logger.info("Creating superuser with email='%s'", email)
return self._create_user(email, password, **extra_fields)
def record_login(self, user=None, email=None):
"""
Record a succesful login to last_login for the user by user
obj or email
"""
if email is None and user is None:
raise ValueError("email and user cannot both be None")
if email:
this_user = self.get(email=email)
else:
this_user = user
this_user.last_login = timezone.now()
this_user.save()
class BaseUser(AbstractBaseUser, PermissionsMixin):
"""
Our email for username user model
"""
first_name = models.CharField(_("first name"), max_length=30, blank=True)
last_name = models.CharField(_("last name"), max_length=30, blank=True)
email = models.EmailField(_("email address"), unique=True, db_index=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
data = models.JSONField(default=dict, blank=True, help_text="Arbitrary user data")
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
swappable = "AUTH_USER_MODEL"
abstract = True
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = "%s %s" % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email], **kwargs)
def save(self, *args, **kwargs):
"""Ensure email is always lower case"""
self.email = self.email.lower()
return super().save(*args, **kwargs)
class Badge(models.Model):
name = models.CharField(_("name"), max_length=100, blank=True)
display_name = models.CharField(_("display name"), max_length=100, blank=True)
class User(BaseUser):
badges = models.ManyToManyField(Badge)
github_username = models.CharField(_("github username"), max_length=100, blank=True)
image = models.FileField(upload_to="profile-images", null=True, blank=True)
def save_image_from_github(self, avatar_url):
response = requests.get(avatar_url)
base_filename = f"{slugify(self.get_full_name())}-profile"
filename = f"{base_filename}.png"
img_path = os.path.join(
settings.MEDIA_ROOT, "media", "profile-images", filename
)
with open(filename, "wb") as f:
f.write(response.content)
reopen = open(filename, "rb")
django_file = File(reopen)
self.image.save(filename, django_file, save=True)
class LastSeen(models.Model):
"""
Last time we saw a user. This differs from User.last_login in that
a user may login on Monday and visit the site several times over the
next week before their login cookie expires. This tracks the last time
they were actually on the web UI.
So why isn't it on the User model? Well that would be a lot of database
row churn and contention on the User table itself so I'm breaking this
out into another table. Likely a pre-optimization on my part.
Far Future TODO: Store and update this in Redis as it happens and daily
sync that info to this table.
"""
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
related_name="last_seen",
on_delete=models.CASCADE,
)
at = models.DateTimeField(default=timezone.now)
def now(self, commit=True):
"""
Update this row to be right now
"""
self.at = timezone.now()
if commit:
self.save()
@receiver(post_save, sender=User)
def create_last_seen_for_user(sender, instance, created, raw, **kwargs):
"""Create LastSeen row when a User is created"""
if raw:
return
if created:
LastSeen.objects.create(user=instance, at=timezone.now())