102 lines
3.4 KiB
Python
102 lines
3.4 KiB
Python
import os
|
|
import uuid
|
|
|
|
import magic
|
|
|
|
from django import forms
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
from django.utils.text import normalize_newlines
|
|
from django.core.exceptions import ValidationError
|
|
|
|
|
|
class Thought(models.Model):
|
|
text = models.CharField(max_length=140, blank=True)
|
|
extended_text = models.TextField(blank=True)
|
|
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
|
|
posted = models.DateTimeField(auto_now_add=True)
|
|
timezone_offset = models.IntegerField() # The number of minutes behind UTC we were when this was posted
|
|
media = models.FileField(upload_to="", blank=True) # A single image, video, or sound clip per post
|
|
media_alt = models.TextField(blank=True) # An optional transcription of the Thought's media
|
|
|
|
def get_media_type(self):
|
|
if not self.media:
|
|
return ""
|
|
else:
|
|
return os.path.splitext(self.media.path)[1][1:]
|
|
|
|
def get_timezone(self):
|
|
return timezone.get_fixed_timezone(-self.timezone_offset)
|
|
|
|
def get_offset_hours(self):
|
|
offset_hours = -self.timezone_offset / 60
|
|
# Convert 4.0 to 4
|
|
if offset_hours == int(offset_hours):
|
|
offset_hours = int(offset_hours)
|
|
if offset_hours > 0:
|
|
offset_hours = "+" + str(offset_hours)
|
|
return str(offset_hours)
|
|
|
|
def get_absolute_url(self):
|
|
return f"/?show={str(self.uuid)}"
|
|
|
|
|
|
# Honestly I'm so sick of this problem that writing out a comment here to explain why it is necessary is beyond me.
|
|
# I'm calling this CharField and not MySpecialLineNormalizingCharField
|
|
# because I think this should be the default behavior and I don't understand why it's not.
|
|
class CharField(forms.CharField):
|
|
def to_python(self, value):
|
|
# I am assuming that value is always a string and normalize_newlines isn't going to error on any string input
|
|
return super().to_python(normalize_newlines(value))
|
|
|
|
|
|
# A dict mapping allowed mime types to file extensions
|
|
ALLOWED_MEDIA_TYPES = {
|
|
"image/png": "png",
|
|
"image/jpeg": "jpeg",
|
|
"audio/x-m4a": "m4a",
|
|
"audio/mp3": "mp3",
|
|
"video/mp4": "mp4",
|
|
"video/quicktime": "mov",
|
|
}
|
|
|
|
|
|
class ThoughtForm(forms.ModelForm):
|
|
def clean_media(self):
|
|
f = self.cleaned_data["media"]
|
|
if not f:
|
|
return f
|
|
|
|
# 16 MB file size limit.
|
|
if f.size > 2**24:
|
|
raise ValidationError("Content too long", code="max_size")
|
|
|
|
chunk = next(f.chunks(chunk_size=2048))
|
|
media_type = magic.from_buffer(chunk, mime="True")
|
|
|
|
if media_type not in ALLOWED_MEDIA_TYPES:
|
|
raise ValidationError("Invalid media type", code="invalid")
|
|
|
|
return self.cleaned_data["media"]
|
|
|
|
class Meta:
|
|
model = Thought
|
|
exclude = ["posted"]
|
|
widgets = {
|
|
"text": forms.Textarea(attrs={"class": "thought", "rows": 1, "placeholder": "What are you thinking?"}),
|
|
"extended_text": forms.Textarea(attrs={"class": "thought", "rows": 1, "placeholder": "Anything else?"}),
|
|
"timezone_offset": forms.NumberInput,
|
|
"media_alt": forms.Textarea(attrs={"placeholder": "Media alternate text, please", "rows": 1})
|
|
}
|
|
field_classes = {
|
|
"text": CharField,
|
|
"extended_text": CharField,
|
|
"media_alt": CharField,
|
|
}
|
|
|
|
error_messages = {
|
|
"text": {
|
|
"max_length": "Text must be at most 140 characters."
|
|
},
|
|
}
|