Skip to main content
 首页 » 编程设计

python之尝试更新单个 Django 表单/模型字段失败,因为未提供用户 ID(外键)

2024年08月27日27java哥

我有一个看起来很糟糕但暂时还不错的页面,它显示了用户的个人资料图片(这个用户没有),以及“他们成为粉丝的年份”。两者都是表单,因此可以更新。

所以这两种形式只有一个元素。这是年度粉丝表(下面是完整的 forms.py):

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',) 

我有这个功能

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; 

这个 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) 

但是在提交粉丝年表格时,profile.save() 行(在顶部函数中)导致了这个错误:

“user_id”列中的空值违反了非空约束...详细信息:失败行包含 (26, null, 1964, )。。

我如何将这个单一的字段值(它是 UserProfile 模型中的一个字段)关联到它所针对的用户——当前登录的用户?


模型.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. 

models.py 的底部,包含 UserProfile 模型:

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 

表单.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 

请您参考如下方法:

如果你只是想更新,你需要像这样传递 UserProfile 实例:

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

然后照常调用..

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 

祝你好运!