So I've built a very basic blog in Django. I have been working on adding a new feature allowing me to save a new post as a draft to publish later, or to unpublish an already published post. I'm fairly new to Django/Python, so perhaps I'm making a novice mistake here.
To achieve this, I've added two fields to my Post model. A BooleanField
named published and a DateTimeField
named publish_date. I only ever want publish_date to update when publish is set to True
& was previously False
. The approach I've taken is to override the save()
method of my Post model. Within the method, I am either setting publish_date to None
(if published is unchecked on the form) or I am setting publish_date to timezone.now() (if published is checked on the form).
This approach is okay, although publish_date will be updated everytime the post is updated, even if it has not been unpublished/republished. This cannot happen. I'm still learning about the interactions between views, models, and forms, so I really appreciate any guidance.
Please take a look at all relevant code and let me know of a better approach to solving this. Thank you for your help!
models.py
class Post(models.Model):
title = models.CharField(max_length=255)
body = RichTextUploadingField(blank=True, null=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
header_image = models.ImageField(upload_to="images/header_images/", default='images/header_images/default.jpg')
slug = models.SlugField(max_length=255, blank=True, null=True)
snippet = models.CharField(max_length=255)
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True, blank=False, null=False)
publish_date = models.DateTimeField(auto_now_add=False, blank=True, null=True)
likes = models.ManyToManyField(User, related_name='blog_post', blank=True)
published = models.BooleanField()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('article-details', kwargs={"pk":self.pk,"slug":self.slug})
def total_likes(self):
return self.likes.count()
def save(self, *args, **kwargs):
if self.published == True:
self.publish_date = timezone.now()
else:
self.publish_date = None
super(FakePost, self).save(*args, **kwargs)
Forms.py
class ArticleNewForm(forms.ModelForm):
class Meta:
model = Post
fields = (
'title',
'slug',
'author',
'header_image',
'body',
'snippet',
'publish_date',
'published',
)
widgets = {
'title': forms.TextInput(),
'author': forms.TextInput(attrs={'class':'form-control', 'value':'','id':'userfield','type':'hidden',}),
'body': forms.Textarea(),
'snippet': forms.Textarea(),
'publish_date': DateTimeInput(attrs={'type': 'datetime-local'}),
}
You can avoid using the published all along, and set the publish date to "publish" the Post.
from django.utils import timezone
from django.utils.functional import cached_propery
class Post(models.Model):
# ...
publish_date = models.DateTimeField(blank=True, null=True, default=None)
@cached_property
def is_published(self):
return self.publish_date <= timezone.now()
def publish(self):
self.publish_date = timezone.now()
Check if is published in template:
{% if post.is_published %}
<span>Published!</span>
{% endif %}
Publish a post:
post = Post()
post.publish()
post.save()
QuerySet for all published posts:
Post.objects.filter(publish_date__isnull=False).all()
And the opposite, posts yet to be published:
Post.objects.filter(publish_date__isnull=True).all()
Publish all posts in a QuerySet:
Post.objects.update(publish_date=timezone.now())
And even, you can implement a future publishing, e.g. in 3 days:
Post.objects.update(publish_date=timezone.now() + timezone.timedelta(days=3))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With