2

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'}),
        }

2 Answers2

1

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))
Dan Yishai
  • 726
  • 3
  • 12
0

To avoid that publish_date will be updated everytime the post is updated, even if it has not been unpublished/republished you should check if the new value of self.published has changed from the previous and if he does, do your code.

Django natively does not give you a simple tool to check if the values have changed so you will have to use some answer of this question.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Tiki
  • 760
  • 1
  • 8
  • 16
  • 1
    Thank you for this. I had found that question earlier and have tried working through it to no avail, but your comment reassures me that it was the right direction. I will continue trying. Thank you! – Johnny McNeil Mar 25 '21 at 22:02
  • This was the solution, I needed to look more closely in the comments of some of the other users in the accepted answer there. Thank you! – Johnny McNeil Mar 27 '21 at 03:25