Django ModelForm not saving data to database
Asked Answered
M

6

6

A Django beginner here having a lot of trouble getting forms working. Yes I've worked through the tutorial and browsed the web a lot - what I have is mix of what I'm finding here and at other sites. I'm using Python 2.7 and Django 1.5. (although the official documentation is extensive it tends to assume you know most of it already - not good as a beginners reference or even an advanced tutorial)

I am trying to create a form for "extended" user details - eg. company name, street address, etc but the form data is not being saved to the database.

Initially I tried to create a model extending the standard User model, but I gave up on that - too much needed modifying and it was getting into nitty gritty that was way beyond me at this stage. So instead I have created a new model called UserProfile:

class UserProfile(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, unique=True) 
    company = models.CharField(max_length=50, blank=True)
    address1 = models.CharField(max_length=50, blank=True)
    address2 = models.CharField(max_length=50, blank=True)
    city = models.CharField(max_length=20, blank=True)
    region = models.CharField(max_length=20, blank=True)
    postcode = models.CharField(max_length=10, blank=True)
    country = models.CharField(max_length=20, blank=True)
    phone = models.CharField(max_length=30, blank = True)

I have seen different references online as to whether I should link to the User model with a ForeignKey (as above) or with a OneToOne.

I am trying to use a ModelForm (keep it simple for what should be a simple form). Here is my forms.py

from django.forms import ModelForm
from .models import UserProfile   

class UserDetailsForm(ModelForm):
    class Meta:
        model = UserProfile
        fields = ['company','address1','address2','city','region', 'postcode','country','phone']

Here is my view:

def UserDetailsView(request):
    #f = 0
    if request.method == 'POST':
        f = UserDetailsForm(request.POST, instance = request.user)
        if f.is_valid():
            f.save()
    else:
        f = UserDetailsForm(request.POST , instance = request.user)

    print "UserDetails objects: ", (UserProfile.objects.all()) 

    return render_to_response('plagweb/console/profile.html', 
                              { 'form': f}, 
                              context_instance=RequestContext(request))     

(yes there is an inconsistency in UserProfile vs UserDetail - this is a product of all my hacking and will be fixed once I get it working)

Diagnostics show f.is_valid() returning True. Similarly, diagnostics show UserProfile.objects.all() as being empty. I tried this in the above view after the save(), and also at the Django console.

Here is my template:

 <form method="POST" action="">
 <table>{{ form }}</table>
 <input type="submit" value="Update" />
 {% csrf_token %}
 </form>

At the moment the main problem is that form data is not being saved to the database. I do not know if is being read yet or not (once I have some data in the database...)

One thought is that the User relationship might be causing a problem?


Addenda, following on from Daniel Roseman's useful comment/help:

Now the form is saving correctly (confirmed with diagnostics and command line checks. However when I go back to the form, it is not displaying the existing data. A form with empty data fields is displayed. As far as I can tell, I'm passing the instance data correctly.

Is there a ModelForm setting that needs to change?

Here's the modified View:

def UserDetailsView(request):
    #print "request co:", request.user.profile.company
    f = UserDetailsForm(request.POST, instance = request.user.profile )

    if request.method == 'POST':
        if f.is_valid():
            profile = f.save(commit=False)
            profile.user = request.user
            profile.save()

    return render_to_response('plagweb/console/profile.html', 
                              { 'form': f}, 
                              context_instance=RequestContext(request))     

The diagnostics show that request.user.profile is set correctly (specifically the company field). However the form is displayed with empty fields. The HTML source doesn't show any data values, either. As an extra check, I also tried some template diagnostics:

<table border='1'>
    {% for field in form%}
        <tr>
            <td>{{field.label}}</td>
            <td>{{field.value}}</td>
        </tr>
    {% endfor%}
</table>

This lists the field labels correctly, but the values are all reported as None.

For completeness, the UserProfile's user field is now defined as user = models.OneToOneField(settings.AUTH_USER_MODEL) and the User.profile lambda expression is unchanged.

Margarite answered 17/8, 2013 at 0:45 Comment(2)
Maybe try taking out the if f.is_valid() to see what exception it might throw when you try to save it.Gush
It doesn't throw any exceptions. Also my diagnostic shows is_valid() as returning trueMargarite
I
20

I'm not sure exactly what the problem is here, but one issue certainly is that you are passing instance=request.user when instantiating the form. That's definitely wrong: request.user is an instance of User, whereas the form is based on UserProfile.

Instead, you want to do something like this:

f = UserDetailsForm(request.POST)
if f.is_valid():
    profile = f.save(commit=False)
    profile.user = request.user
    profile.save()

As regards ForeignKey vs OneToOne, you should absolutely be using OneToOne. The advice on using ForeignKeys was given for a while in the run-up to the release of Django 1 - almost five years ago now - because the original implementation of OneToOne was poor and it was due to be rewritten. You certainly won't find any up-to-date documentation advising the use of FK here.

Edit after comments The problem now is that you're instantiating the form with the first parameter, data, even when the request is not a POST and therefore request.POST is an empty dictionary. But the data parameter, even if it's empty, takes precedence over whatever is passed as initial data.

You should go back to the original pattern of instantiating the form within the if statement - but be sure not to pass request.POST when doing it in the else clause.

Irra answered 17/8, 2013 at 9:4 Comment(6)
re. ForeignKey: I think this highlights a big part of my problem. There is a LOT of stuff in blogs, stackoverflow, etc but it covers everything from 1.0 to 1.5, and it isn't always clear which version is being addressed. Even the Apress book (Holovaty&Kaplan-Moss) has a latest edition "modified for 1.1". I'm sure it will all make sense once I get into it further but it is confusing for those in the learning curve!Margarite
re. request.user: That appears to have fixed the save. UserProfile.objects.all() returns an object and at the command line, I can read one of my fields! So that fixes my immediate problem. Now I need to load that data when the form is loaded. Looks like I need to work on the second UserDetailsForm constructor...Margarite
No, no need for a new constructor: just pass the UserProfile as instance.Irra
I currently the constructor set as: f = UserDetailsForm(request.POST, instance = request.user.profile) This appears to do the update correctly (checked via the command line), but it doesn't load the existing data into the form. I'm thinking the RequestContext might be incorrect?Margarite
I've added an addenda to my question with an update of the code so far. Data is now being saved (thanks!). Going back to the form, the data is being loaded, but it is not being displayed in the ModelForm.Margarite
That did it thanks - I think I misunderstood what you meant by "no need for a new constructor". I owe you a beer next time I'm in the UK :-)Margarite
M
2

This is kind of old thread, but I hope this will help someone. I've been struggling with similar issue, I initiated object from db, updated field, called save() function and nothing happened.

user = User.objects.get(username = example_string)
user.userprofile.accepted_terms = True
user.userprofile.save()

The field accepted_terms have been added lately and that was the issue. First try python manage.py makemigrations and python manage.py migrate, if this does not help you need to flush the database.

Solution in my case was flushing the database.

Mauchi answered 18/5, 2019 at 9:27 Comment(0)
T
2

Remember to register your model form to admin.py file. This is a common mistake

Register your models here.

from . models import advancedUserForm
admin.site.register(advancedUserForm)
Tears answered 29/6, 2019 at 8:55 Comment(0)
S
0
UserDetailsForm(request.POST, instance = db_obj_form)

db_obj should be an object of table UserProfile that you want to save.

Singletree answered 17/8, 2013 at 9:20 Comment(0)
C
0

I think you need to 1st create an object for ModelForm and then assign the object on if the request method is post. In your code you are only doing this for only one attribute and before checking the method. -Also in your forms.py ,in fields parameter of Meta() class, you used [] brackets which indicates what attributes must be excluded, but as your including all those attributes in form you must use () brackets. I think your code should be edited as follows:

see here

Cent answered 21/5, 2020 at 15:36 Comment(0)
K
0

Wherever you try to submit a form data to your Django code, then the Django will createan object to entire form data. The object is look like this form_obj = request.method_name. Here method_name is the form method that is (POST or GET) ex:- form_obj = request.POST

If you want to get the form data then use a function request.method_name.get(name) here name is form attribute assign value

ex:-

<form method="post">
{% csrf_token %}
{form}
</form>

in view :-

import statements as you required
def your_function(request):
  # you can creat a object to the form by
  form_obj = myform()
  if request.method =="POST":
    form_obj = myform(request.POST)
    if form_obj.is_valid():

      # you can directly save by use commit()
      form_obj.save(commit=True)
       #   or
      form_obj1 = form_obj.save(commit=False)
      #which  do you want to save in database ,those fields you need to specify like below 
      form_obj1.attribute1 = form_obj.cleaned_data["name of form field"]
        '
        '
      form_obj1.save()

and in Myform.py file

import statments    
class myform(forms.ModelForm):
    class meta():
        model = your_models_name
        fields =("attribute1","attribute2".....) # included attribute of your_model
Kev answered 21/5, 2020 at 16:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.