models.py 5.22 KB
Newer Older
I. Welch Canavan's avatar
I. Welch Canavan committed
1
2
3
4
5
6
7
8
"""
content models
"""

import mistune
from django.db import models
from django.utils import timezone
from django.utils.safestring import mark_safe
Welch Canavan's avatar
Welch Canavan committed
9
from dirtyfields import DirtyFieldsMixin
10
11
12
13
14
15
from django.core.files.base import ContentFile

# Image variations
from io import BytesIO
from PIL import Image as PillowImage, ImageOps

I. Welch Canavan's avatar
I. Welch Canavan committed
16

Welch Canavan's avatar
Welch Canavan committed
17
# Markdown
I. Welch Canavan's avatar
I. Welch Canavan committed
18
19
20
21
22
from .utils import HighlightRenderer

renderer = HighlightRenderer()
markdown = mistune.Markdown(renderer=renderer)

Welch Canavan's avatar
Welch Canavan committed
23
24
25

# Shortlinks
import requests
Welch Canavan's avatar
Welch Canavan committed
26
import os
Welch Canavan's avatar
Welch Canavan committed
27
28


29
30
31
32
33
34
# upload directory

import hashlib
import datetime


I. Welch Canavan's avatar
I. Welch Canavan committed
35
# Image Types
36
37
38
39
class Image(DirtyFieldsMixin, models.Model):
    date_hash = hashlib.md5(f"{datetime.date.today()}".encode('utf-8')).hexdigest()[:10]
    file_path = f"uploads/{date_hash}"

I. Welch Canavan's avatar
I. Welch Canavan committed
40
41
42
43
44
45
46
47
    caption = models.CharField(max_length=500)
    height = models.IntegerField()
    related_post = models.ForeignKey(
        "Post", on_delete=models.CASCADE, blank=True, null=True, related_name="images"
    )
    related_work = models.ForeignKey(
        "Work", on_delete=models.CASCADE, blank=True, null=True, related_name="images"
    )
48
    thumbnail = models.ImageField(null=True, upload_to=file_path)
Welch Canavan's avatar
Welch Canavan committed
49
    thumbnail_webp = models.ImageField(null=True, upload_to=file_path)
50
    upload = models.ImageField(height_field="height", width_field="width", upload_to=file_path)
I. Welch Canavan's avatar
I. Welch Canavan committed
51
    uploaded_at = models.DateTimeField(auto_now_add=True)
52
    webp = models.ImageField(null=True, upload_to=file_path)
I. Welch Canavan's avatar
I. Welch Canavan committed
53
54
    width = models.IntegerField()

55
56
    def set_thumbnail(self):
        original_image = PillowImage.open(self.upload)
Welch Canavan's avatar
Welch Canavan committed
57
        filename = f"{self.upload.name.split('.')[0]}.thumbnail.jpg"
58
        image_io = BytesIO()
Welch Canavan's avatar
Welch Canavan committed
59
        image = ImageOps.fit(original_image, (500, 500), PillowImage.ANTIALIAS)
60
61
62
63
64
65
66
67
68
        image = image.convert(mode="RGB")
        image.save(image_io, "JPEG", quality=70, optimize=True, progressive=True)

        self.thumbnail.save(
            filename,
            ContentFile(image_io.getvalue()),
            save=False
        )

Welch Canavan's avatar
Welch Canavan committed
69
70
71
72
73
74
75
76
77
78
79
80
81
    def set_thumbnail_webp(self):
        original_image = PillowImage.open(self.upload)
        filename = f"{self.upload.name.split('.')[0]}.thumbnail.webp"
        image_io = BytesIO()
        image = ImageOps.fit(original_image, (500, 500), PillowImage.ANTIALIAS)
        image.save(image_io, "WEBP", quality=70, method=6)

        self.thumbnail_webp.save(
            filename,
            ContentFile(image_io.getvalue()),
            save=False
        )

82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
    def set_webp(self):
        original_image = PillowImage.open(self.upload)
        filename = f"{self.upload.name.split('.')[0]}.webp"
        image_io = BytesIO()
        image = original_image
        image.save(image_io, "WEBP", quality=70, method=6)

        self.webp.save(
            filename,
            ContentFile(image_io.getvalue()),
            save=False
        )

    def save(self, *args, **kwargs):
        if not self.pk or 'upload' in self.get_dirty_fields():
            self.set_thumbnail()
Welch Canavan's avatar
Welch Canavan committed
98
            self.set_thumbnail_webp()
99
100
101
102
            self.set_webp()

        super().save(*args, **kwargs)

I. Welch Canavan's avatar
I. Welch Canavan committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
    class Meta:
        db_table = "image"

    def preview(self):
        url = self.upload.url

        if url:
            return mark_safe(
                '<img class="image-preview" src="{0}">'.format(self.upload.url)
            )

        return None

    def __str__(self):
        return self.caption


# Content Types
class ContentBase(models.Model):
    """Base Content Abstract Model"""

124
    description = models.TextField(blank=True, null=True)
I. Welch Canavan's avatar
I. Welch Canavan committed
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
    hero = models.OneToOneField(Image, blank=True, null=True, on_delete=models.CASCADE)
    markdown = models.TextField()
    published_at = models.DateTimeField("date published", default=timezone.now)
    slug = models.SlugField(primary_key=True)
    title = models.CharField(max_length=500)

    class Meta:
        abstract = True

    def __str__(self):
        return self.title

    def body(self):
        return markdown(self.markdown)


Welch Canavan's avatar
Welch Canavan committed
141
class Post(DirtyFieldsMixin, ContentBase):
I. Welch Canavan's avatar
I. Welch Canavan committed
142
143
144
145
    DRAFT = "draft"
    PUBLISHED = "published"
    POST_STATUS_CHOICES = ((DRAFT, "Draft"), (PUBLISHED, "Published"))

Welch Canavan's avatar
Welch Canavan committed
146
    shortlink = models.CharField(max_length=20, null=True, blank=True, default='')
I. Welch Canavan's avatar
I. Welch Canavan committed
147
148
149
150
151
    status = models.CharField(max_length=20, choices=POST_STATUS_CHOICES, default=DRAFT)

    class Meta:
        db_table = "post"

Welch Canavan's avatar
Welch Canavan committed
152
153
154
155
156
    def set_shortlink(self):
        url = "https://api-ssl.bitly.com/v4/shorten"
        headers = {
            "Host": "api-ssl.bitly.com",
            "Accept": "application/json",
Welch Canavan's avatar
Welch Canavan committed
157
            "Authorization": f"Bearer {os.environ.get('BITLY_TOKEN')}"
Welch Canavan's avatar
Welch Canavan committed
158
159
        }
        payload = {
Welch Canavan's avatar
Welch Canavan committed
160
            "group_guid": os.environ.get('BITLY_GROUP_GUID'),
Welch Canavan's avatar
Welch Canavan committed
161
162
163
164
            "long_url": f"https://welchcanavan.com/{self.slug}"
        }
        r = requests.post(url, headers=headers, json=payload)

Welch Canavan's avatar
Welch Canavan committed
165
166
        if r.status_code is 200:
            self.shortlink = r.json()[u'id']
Welch Canavan's avatar
Welch Canavan committed
167
168

    def save(self, *args, **kwargs):
Welch Canavan's avatar
Welch Canavan committed
169
        if not self.shortlink or 'slug' in self.get_dirty_fields():
Welch Canavan's avatar
Welch Canavan committed
170
171
172
            self.set_shortlink()

        super().save(*args, **kwargs)
I. Welch Canavan's avatar
I. Welch Canavan committed
173
174
175
176
177
178
179
180
181
182

class Work(ContentBase):
    ART = "art"
    CODE = "code"
    WORK_TYPE_CHOICES = ((CODE, "Code"), (ART, "Art"))

    type = models.CharField(max_length=20, choices=WORK_TYPE_CHOICES, default=None)

    class Meta:
        db_table = "work"