aliteralmind
aliteralmind

Reputation: 20163

Attempting to update a single Django form/model field is failing because user-id (foreign key) is not provided

I have this horrible-looking-but-good enough-for-the-moment page, that displays the user's profile picture (this user has none), and the "year they became a fan". Both are also forms so they can be updated.

enter image description here

So these two forms have exactly one element. Here is the year-became-a-fan form (full forms.py below):

YEAR_CHOICES = ((x,str(x)) for x in range(DISCOVERED_MIN_YEAR, DISCOVERED_MAX_YEAR+1))
YEAR_DISCOVERED = forms.IntegerField(label="Year discovered Billy Joel's music/became a fan", required=False,
    min_value=DISCOVERED_MIN_YEAR, max_value=DISCOVERED_MAX_YEAR,
    widget=forms.Select(choices=YEAR_CHOICES))

class UserProfileFormYearDiscOnly(forms.ModelForm):
    global  YEAR_DISCOVERED
    year_discovered = YEAR_DISCOVERED
    class Meta:
        model = UserProfile
        fields = ('year_discovered',)

I have this function

def _update_form_on_favs(user_profile_form):
    """
        Submit the profile picture OR year_discovered form as found on the "my favorites" page.

        RETURNS
            - None: If the form is valid
            - The form, if any errors are detected.
    """
    if(user_profile_form.is_valid()):
        profile = user_profile_form.save(commit=False)
        profile.save()
        return  None;

return  user_profile_form;

Which is called by this view:

@login_required
def my_favorites(request, form_name=None):
    context = RequestContext(request)

    context["fav_albums"] = request.user.album_set.all()
    context["fav_songs"] = request.user.song_set.all()

    profile_pic_form = None
    year_disc_form = None
    if(request.POST  and  form_name is not None):
        if(form_name == "profile_pic"):
            profile_pic_form = _update_form_on_favs(UserProfileFormProfilePicOnly(request.POST))
        elif(form_name == "year_disc"):
            year_disc_form = _update_form_on_favs(UserProfileFormYearDiscOnly(request.POST))
        else:
            raise  ValueError("Unknown value for 'form_name': '%s'" % str(form_name))

    if(profile_pic_form is None):
        #The form was either successfully submitted, or not submitted at all
        context["profile_pic_form"] = UserProfileFormProfilePicOnly()

    if(year_disc_form is None):
        #The form was either successfully submitted, or not submitted at all
        context["year_disc_form"] = UserProfileFormYearDiscOnly(initial={
            "year_discovered": request.user.profile.year_discovered})

return  render(request, "billyjoel/my_favorites.html", context_instance=context)

But when submitting the fan-year form, the profile.save() line (in the top function) is causing this error:

null value in column "user_id" violates not-null constraint...DETAIL: Failing row contains (26, null, 1964, )..

How do I associate this single field-value (which is a field in the UserProfile model) to the user it's meant for--the currently-logged-in user?


models.py:

from datetime                   import datetime
from django.contrib.auth.models import User
from django.core.exceptions     import ValidationError
from django.db                  import models
from time                       import time

def get_upload_file_name(instance, filename):
    return  "uploaded_files/%s_%s" % (str(time()).replace(".", "_"), filename)

class Album(models.Model):
    OFFICIALITY = (
        ('J', 'Major studio release'),
        ('I', 'Non-major official release'),
        ('U', 'Unofficial'),
    )
    title = models.CharField(max_length=70)
    description = models.TextField(max_length=500, default="", null=True, blank=True)
    pub_date = models.DateField('release date')
    officiality = models.CharField(max_length=1, choices=OFFICIALITY)
    is_concert = models.BooleanField(default=False)
    main_info_url = models.URLField(blank=False)
    thumbnail = models.FileField(upload_to=get_upload_file_name, blank=True, null=True)

    #virtual field to skip over the through table.
    songs = models.ManyToManyField("Song", through="AlbumSong")

    users_favorited_by = models.ManyToManyField('auth.User')

    def __str__(self):
        return  self.title

    class Meta:
        #Default ordering is by release date, ascending.
        ordering = ['pub_date']


class Song(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(max_length=500, default="", null=True, blank=True)
    length_seconds = models.IntegerField()
    lyrics_url = models.URLField(default="", blank=True, null=True)
    albums = models.ManyToManyField("Album", through="AlbumSong")

    users_favorited_by = models.ManyToManyField(User)

    def get_length_desc_from_seconds(self):
        if(self.length_seconds == -1):
            return  "-1"
        m, s = divmod(self.length_seconds, 60)
        h, m = divmod(m, 60)
        if(h):
            return  "%d:%02d:%02d" % (h, m, s)
        else:
            return  "%d:%02d" % (m, s)

    def __str__(self):
        return  self.name

class AlbumSong(models.Model):
    song = models.ForeignKey(Song)
    album = models.ForeignKey(Album)
    sequence_num = models.IntegerField()

    class Meta:
        unique_together = ('album', 'sequence_num',)
        unique_together = ('album', 'song',)

    def __str__(self):
        return  str(self.album) + ": " + str(self.sequence_num) + ": " + str(self.song)

DISCOVERED_MIN_YEAR = 1960
DISCOVERED_MAX_YEAR = datetime.now().year

def validate_discovered_year(value):
    intval = -1
    try:
        intval = int(str(value).strip())
    except TypeError:
        raise ValidationError(u'"%s" is not an integer' % value)

    global  DISCOVERED_MAX_YEAR
    global  DISCOVERED_MIN_YEAR

    if(intval < DISCOVERED_MIN_YEAR  or  intval > DISCOVERED_MAX_YEAR):
        raise ValidationError(u'%s is an invalid "discovered Billy Joel year". Must be between %s and %s, inclusive' % value, DISCOVERED_MAX_YEAR, DISCOVERED_MIN_YEAR)

    #It's all good.

The bottom of models.py, containing the UserProfile model:

class UserProfile(models.Model):
    """
       select id from auth_user where username='jeffy7';
       select * from billyjoel_userprofile where user_id=XXX;    
    """
    # This line is required. Links UserProfile to a User model instance.
    user = models.OneToOneField(User, related_name="profile")

    # The additional attributes we wish to include.
    year_discovered = models.IntegerField(blank=True,
        verbose_name="Year you discovered Billy Joel's music/became a fan",
        validators=[validate_discovered_year])
    profile_picture = models.ImageField(upload_to=get_upload_file_name, blank=True, null=True)

    #https://github.com/mfogel/django-simple-email-confirmation
    #activation_key = models.CharField(maxlength=40)
    #key_expires = models.DateTimeField()

    # Override the __unicode__() method to return out something meaningful!
    def __unicode__(self):
        return self.user.username

forms.py:

from  django import forms
from  django.contrib.auth.models import User
from  .models import UserProfile, DISCOVERED_MIN_YEAR, DISCOVERED_MAX_YEAR

class UserForm(forms.ModelForm):
    password1 = forms.CharField(label="Password", widget=forms.PasswordInput())
    password2 = forms.CharField(label="Password confirmation", widget=forms.PasswordInput())

    class Meta:
        model = User
        fields = ('username', 'email', 'password1', 'password2')

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super(UserForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password2"])
        if commit:
            user.save()
        return user

#Should be implemented as an abstract base class, with Meta or Meta.fields passed into the constructor...START
YEAR_CHOICES = ((x,str(x)) for x in range(DISCOVERED_MIN_YEAR, DISCOVERED_MAX_YEAR+1))
YEAR_DISCOVERED = forms.IntegerField(label="Year discovered Billy Joel's music/became a fan", required=False,
    min_value=DISCOVERED_MIN_YEAR, max_value=DISCOVERED_MAX_YEAR,
    widget=forms.Select(choices=YEAR_CHOICES))

#With no bounds:
#YEAR_DISCOVERED = forms.IntegerField(min_value=DISCOVERED_MIN_YEAR, max_value=DISCOVERED_MAX_YEAR)
class UserProfileForm(forms.ModelForm):
    global  YEAR_DISCOVERED
    year_discovered = YEAR_DISCOVERED
    class Meta:
        model = UserProfile
        fields = ('year_discovered', 'profile_picture',)

class UserProfileFormProfilePicOnly(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ('profile_picture',)

class UserProfileFormYearDiscOnly(forms.ModelForm):
    global  YEAR_DISCOVERED
    year_discovered = YEAR_DISCOVERED
    class Meta:
        model = UserProfile
        fields = ('year_discovered',)
#Should be implemented as an abstract base class, with Meta or Meta.fields passed into the constructor...END

Upvotes: 2

Views: 359

Answers (1)

zkanda
zkanda

Reputation: 724

If you just want update, you need to pass the UserProfile instance like this:

_update_form_on_favs(UserProfileFormProfilePicOnly(instance=request.user.profile, request.POST))

Then call it as usual..

def _update_form_on_favs(profile_form_w_user_set_into_cnstr):

    if(profile_form_w_user_set_into_cnstr.is_valid()):
        profile = profile_form_w_user_set_into_cnstr.save(commit=False)
        profile.save()

    return  profile_form_w_user_set_into_cnstr

Good luck!

Upvotes: 3

Related Questions