dirtycoder/pets

View on GitHub
pets/meupet/models.py

Summary

Maintainability
A
0 mins
Test Coverage
import hashlib

from autoslug import AutoSlugField
from django_extensions.db.models import TimeStampedModel
from easy_thumbnails.exceptions import InvalidImageFormatError
from easy_thumbnails.files import get_thumbnailer
from raven.contrib.django.raven_compat.models import client as raven_client

from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils import crypto, timezone
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _

from meupet import services
from users.models import OwnerProfile


class PetQuerySet(models.QuerySet):
    def _filter_by_kind(self, kind):
        try:
            return self.actives().filter(kind__id=int(kind)).select_related("city")
        except ValueError:
            return self.actives().filter(kind__slug=kind).select_related("city")

    def get_unpublished_pets(self):
        return self.filter(published=False)

    def get_staled_pets(self):
        """
        Pets considered as staled are not modified after a given
        number of days and don't have a request_sent date
        """
        stale_date = timezone.now() - timezone.timedelta(days=settings.DAYS_TO_STALE_REGISTER)
        return self.filter(
            modified__lt=stale_date,
            request_sent__isnull=True,
            status__in=PetStatus.objects.filter(final=False),
        )

    def actives(self):
        """
        Return only pets with active = True
        """
        return self.filter(active=True)

    def get_expired_pets(self):
        """Expired pets have request_sent date older than expected"""
        expire_date = timezone.now() - timezone.timedelta(days=settings.DAYS_TO_STALE_REGISTER)
        return self.filter(active=True, request_sent__lt=expire_date)


class KindManager(models.Manager):
    def count_pets(self, status):
        return (
            self.filter(pet__status__in=status, pet__active=True)
            .annotate(num_pets=models.Count("pet"))
            .order_by("kind")
        )


class Kind(models.Model):
    kind = models.TextField(max_length=100, unique=True)
    slug = AutoSlugField(max_length=30, populate_from="kind")

    objects = KindManager()

    def __str__(self):
        return self.kind


def get_slug(instance):
    city = ""
    if instance.city:
        city = instance.city.name
    return slugify("{}-{}".format(instance.name, city))


class StatusGroup(models.Model):
    slug = AutoSlugField(max_length=64, unique=True, populate_from="name")
    name = models.CharField(max_length=64)

    def __str__(self):
        return self.name


class PetStatus(models.Model):
    group = models.ForeignKey(
        StatusGroup, related_name="statuses", blank=True, null=True, on_delete=models.CASCADE
    )
    code = models.CharField(max_length=32, unique=True)
    description = models.CharField(max_length=64)
    next_status = models.OneToOneField("self", blank=True, null=True, on_delete=models.CASCADE)
    final = models.BooleanField(default=False)

    class Meta:
        ordering = ("description",)

    def __str__(self):
        return self.description

    @property
    def next(self):
        return self.next_status


class Pet(TimeStampedModel):
    MALE = "MA"
    FEMALE = "FE"
    PET_SEX = ((FEMALE, _("Female")), (MALE, _("Male")))
    SMALL = "SM"
    MEDIUM = "MD"
    LARGE = "LG"
    PET_SIZE = ((SMALL, _("Small")), (MEDIUM, _("Medium")), (LARGE, _("Large")))
    owner = models.ForeignKey(OwnerProfile, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)
    description = models.CharField(max_length=500)
    city = models.ForeignKey("cities.City", on_delete=models.CASCADE)
    kind = models.ForeignKey(Kind, null=True, on_delete=models.CASCADE)
    status = models.ForeignKey(
        PetStatus, related_name="pets", blank=True, null=True, on_delete=models.CASCADE
    )
    size = models.CharField(max_length=2, choices=PET_SIZE, blank=True)
    sex = models.CharField(max_length=2, choices=PET_SEX, blank=True)
    profile_picture = models.ImageField(upload_to="pet_profiles", help_text=_("Maximum image size is 8MB"))
    published = models.BooleanField(default=False)  # published on facebook
    request_sent = models.DateTimeField(null=True, blank=True)
    request_key = models.CharField(blank=True, max_length=40)
    active = models.BooleanField(default=True)
    slug = AutoSlugField(max_length=50, populate_from=get_slug, unique=True)

    objects = PetQuerySet.as_manager()

    def get_absolute_url(self):
        return reverse("meupet:detail", kwargs={"pk_or_slug": self.slug})

    def change_status(self):
        self.status = self.status.next
        self.save()

    def is_found_or_adopted(self):
        return self.status.final

    def get_sex(self):
        return dict(self.PET_SEX).get(self.sex)

    def get_size(self):
        return dict(self.PET_SIZE).get(self.size)

    @property
    def thumb_picture(self):
        try:
            return get_thumbnailer(self.profile_picture)["pet_thumb"]
        except InvalidImageFormatError:
            raven_client.captureException()
            return self.profile_picture

    def request_action(self):
        hash_input = (crypto.get_random_string(5) + self.name).encode("utf-8")
        self.request_key = hashlib.sha1(hash_input).hexdigest()

        if not services.send_request_action_email(self):
            return

        self.request_sent = timezone.now()
        self.save(update_modified=False)

    def activate(self):
        self.request_sent = None
        self.request_key = ""
        self.active = True
        self.save()

    def deactivate(self):
        if not services.send_deactivate_email(self):
            return

        self.active = False
        self.save(update_modified=False)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ["-id"]
        unique_together = ("name", "owner")


class Photo(models.Model):
    pet = models.ForeignKey(Pet, on_delete=models.CASCADE)
    image = models.ImageField(upload_to="pet_photos")