Adding and editing model objects using Django class based views and forms


Let us consider a scenario where we create user profile and update/edit the profile of a user using Django forms.

Let us add a simple UserProfile model.

from django.contrib.auth.models import User

class UserProfile(models.Model):
    # Let us add some simple fields to profile
    user = models.OneToOneField(User)
    city = models.CharField(max_length=100)
    country = models.CharField(max_length=10)

    def __unicode__(self):
        return u"%s" % self.user

Now, let us add forms.

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ('city', 'country') #Note that we didn't mention user field here.

    def save(self, user=None):
        user_profile = super(UserProfileForm, self).save(commit=False)
        if user:
            user_profile.user = user
        return user_profile

We did not add "user" in the fields because it is not something that the user adds/selects in the user interface. It should be filled with the currently logged in user so for that purpose we have overridden save method of UserProfileForm. If you observe the save method, we are first trying to get the user profile object without saving it to database(commit=False). This is to make sure that the application doesnt raise IntegrityError(which means we are trying to save to db without entering/filling mandatory fields). Then we are setting the user and then saving to database.

Let us add views to "add new profile" for a user:

from django.views.generic import FormView

class NewUserProfileView(FormView):
    template_name = "profiles/user_profile.html"
    form_class = UserProfileForm

    def form_valid(self, form):
        return super(NewUserProfileView, self).form_valid(form)

    def get_success_url(self, *args, **kwargs):
        return reverse("some url name")

We have overridden form_valid method of NewUserProfileView and passed request.user (current loggedin user) to save method that we have overridden above. Since we cant access request object in models/forms, we need to pass to save method.(we can also pass to init method of form, thats another way).

Now, let us add another view to update the profile object.

from django.views.generic import UpdateView

class EditUserProfileView(UpdateView) #Note that we are using UpdateView and not FormView
    model = UserProfile
    form_class = UserProfileForm
    template_name = "profiles/user_profile.html"

    def get_object(self, *args, **kwargs):
        user = get_object_or_404(User, pk=self.kwargs['pk'])

        # We can also get user object using self.request.user  but that doesnt work
        # for other models.

        return user.userprofile

    def get_success_url(self, *args, **kwargs):
        return reverse("some url name")

    url(r'^profiles/new/$', NewUserProfileView.as_view(), name="new-user-profile"),
    url(r'^users/(?P<pk>\d+)/edit/$', EditUserProfileView.as_view(), name="edit-user-profile"),

Basic Django template to make above forms work:

<form method="post">
  {% csrf_token %}
  {{ form.as_p }}

  <input type=submit value="submit" />

In EditUserProfileView, we have not overridden form_valid because that is not necessary in our case. UpdateView calls by default. Since we need not pass the current user to for an existing profile (as per overridden save method), we need not override form_valid.

Note that we have overridden get_object method because we want to show user id in url rather than profile id but UserProfile is the model for the view(we added model=UserProfile in EditUserProfileView). As per the default get_object method of EditUserProfileView, the "pk" in url refers to the pk of the model UserProfile. But we want to pass user id/pk in url rather than userprofile id. So, we have to override get_object.


Recent Posts






RSS / Atom