Reputation: 20163
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.
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
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