Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A better way of setting values in CreateView?

Tags:

python

django

models.py:

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    post_body = models.TextField()
    user = models.ForeignKey(User)
    pub_date = models.DateTimeField('published')

    def get_absolute_url(self):
        return u'/entries/%d' % self.id

forms.py:

class NewPostForm(forms.ModelForm):
    class Meta:
        model = BlogPost
        fields = ('title', 'post_body')

So, I was trying to make a view for users to post a new post into blog. BlogPost model has 4 fields - title, post_body, user, and pub_date. User should be able to fill the title and post_body, while the other two should be filled automatically. First thing I tried was this:

class NewPostView(generic.edit.CreateView):
    model = BlogPost
    form_class = NewPostForm

    def get_initial(self):
        return {'pub_date': timezone.now(),
                'user': self.request.user}

But the only thing it does is providing the default values for html form. After additional research this is the code that ended up working:

class NewPostView(generic.edit.CreateView):
    model = BlogPost
    form_class = NewPostForm

    def form_valid(self, form):
        obj = form.save(commit=False)
        obj.user = self.request.user
        obj.pub_date = timezone.now()
        obj.save()
        return HttpResponseRedirect(obj.get_absolute_url())

So, while overriding form_valid method worked, I don't feel like it's how it should be done maybe? Is there a better place to put this actions into? What is the conventional way of doing this?

like image 887
M830078h Avatar asked Sep 27 '13 13:09

M830078h


1 Answers

You can override form_valid but then call out to super().form_valid(form) in the return statement. You won't have to worry about saving the form, saving the object, or returning the HttpResponseRedirtect. All of that will be handled by the super. All you need to do is set the values of the form.instance that you want to control behind the scenes.

views.py

class NewPostView(generic.edit.CreateView):
    model = BlogPost
    form_class = NewPostForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        form.instance.pub_date = timezone.now()
        return super().form_valid(form)

That would be a clean approach to adding the values behind the scenes that doesn't require you to do any more logic than adding the values. You can find a reference to that logic in the django documentation at:

https://docs.djangoproject.com/en/2.1/topics/class-based-views/generic-editing/#models-and-request-user


Ideally, for the pub_date you would want to set the field to record the creation time automatically with auto_now_add.

models.py

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    post_body = models.TextField()
    user = models.ForeignKey(User)
    pub_date = models.DateTimeField('published', auto_add_now=True)

    def get_absolute_url(self):
        return u'/entries/%d' % self.id

You can find the documentation on the DateTimeField and its reference to the auto_add_now parameter at link that follows. It also has a auto_add that will update every time the entry is modified, in case you wanted a last_edited_date field.

https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.DateTimeField


Also, there is a possibility that your user could access the form without being logged in. You would either need to handle the case for self.request.user not being set, or you can user the LoginRequiredMixin to only allow logged in users to access this form. I have had an easier time using the mixin.

The resulting view.py with all the above mentioned changes would look like the following.

views.py

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView

from .forms import NewPostForm
from .models import BlogPost

class NewPostView(LoginRequiredMixin, CreateView):
    model = BlogPost
    form_class = NewPostForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        return super().form_valid(form)
like image 57
Gadget Avatar answered Sep 28 '22 00:09

Gadget