Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django NOT NULL constraint failed userprofile.user_id in case of uploading a file

Tags:

python

django

I am trying to practice a simple project: A user registers (using Django registration-redux), uploads some file, and then s/he is provided with a list of her files, being downloadable. Here are my models.py, forms.py, and views respectively:

models.py

class UserProfile(models.Model):
    user = models.ForeignKey(User, related_name='uploaded_by')
    names = models.CharField(max_length=40)
    lastname = models.CharField(max_length=50)
    email = models.EmailField()
    uploads = models.FileField(upload_to= 'blablabla')

    def __str__(self):
        return self.email

forms.py

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ['names', 'uploads']

view.py

from .forms import UserProfileForm
from .models import UserProfile
@login_required()
def new(request):
    form = UserProfileForm(request.POST or None, request.FILES or None)
    if form.is_valid():
        form.save()
    context = {'title': 'welcome', 'form': form}
    return render(requst, 'upform.html', context)

however when I login with a user and try to upload a file I get the error: IntegrityError at /new NOT NULL constraint failed: _userprofile.user_id

After digging a lot I noticed someone suggested the reason of the error is because the user is not included anyhow in the process of posting the form, so I tried whatever came to my mind and the case in which I added the user field to the forms.py worked:

forms.py

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ['names', 'uploads']

the problem however is that the form shown in the browser now includes a drop-down list containing all the registered users. I tried to associate the logged-in user with the form in the views by I kept seeing different errors. My question is: How can I associate the uploads with the logged-in user in a transparent manner. sorry if the question is too newbie-liked

like image 582
Amir Afianian Avatar asked Oct 05 '15 06:10

Amir Afianian


2 Answers

Keep the user out of the form and add it on save:

if form.is_valid():
    profile = form.save(commit=False)
    profile.user = request.user
    profile.save()

I must say your model looks a bit odd; you have multiple profiles for each user, each with a single upload. Seems more likely you want a single profile, with a OneToOne relationship to User, than a separate Uploads model with a ForeignKey to UserProfile.

like image 97
Daniel Roseman Avatar answered Sep 22 '22 05:09

Daniel Roseman


I added the user field to the forms.py worked:

This probably also opens up a security hole, because you could then set the user from outside of your application, overriding the logged-in user.

After digging a lot I noticed someone suggested the reason for the error is because the user is not included anyhow in the process of posting the form.

You figured that quite right. If the user is not a field, the form never knows how to fill the user field of UserProfile. Since you cannot save a UserProfile without a filled in user foreign key, you get the error.

There are various ways around this:

One way to solve the problem is to use save(commit=False) on the form, patch the user into the unsaved instance and manually saving the object to the database:

if form.is_valid():
    profile = form.save(commit=False)
    profile.user = request.user
    profile.save()

This slightly violates the encapsulation because you now handle the database save outside of the form.

You are also able to provide a initial "template" instance to the form:

form = UserProfileForm(
   request.POST,
   request.FILES,
   instance=UserProfile(user=self.request.user)
)

You probably want to do so, anyway because the form also allows to edit an existing userprofile. Currently you are saving a new UserProfile each time and since user is not unique=True in your model class, you will get multiple profiles per user. If you do not want this to be possible, check Daniel Roseman's answer, because then you probably want UserProfile.user to be a OneToOne field.

In this case, we can simplify to

profile, created = UserProfile.objects.get_or_create(user=request.user)
form = UserProfileForm(
    request.POST,
    request.FILES,
    instance=profile
)

Note also that I removed the or None as it is not necessary. BaseForm (which ModelForm derives from) does this check for you (it actually does self.data = data or {}) which essentially undoes the prior none-ification)

like image 43
dhke Avatar answered Sep 24 '22 05:09

dhke