Django, save ModelForm - python

I have created a model Student which extends from the Django User and is a foreign key to another model while it has an integer field called year. What i'm trying to do is to save a form, which has 2 fields. The one is the course id and the another one is the the integer field year. When I'm clicking submit, i'm getting an error Cannot assign "u'2'": "Student.course" must be a "Course" instance.
models.py
class Student(models.Model):
user = models.OneToOneField(User)
course = models.ForeignKey(Course)
year = models.IntegerField(validators=[MinValueValidator(1),
MaxValueValidator(7)])
view.py
def step3(request):
user = request.user
if request.method == 'POST':
form = SelectCourseYear(request.POST)
if form.is_valid():
form.save()
return render_to_response("registration/complete.html", RequestContext(request))
else:
form = SelectCourseYear()
return render(request, 'registration/step3.html',)
forms.py
class SelectCourseYear(forms.ModelForm):
course = forms.CharField()
year = forms.IntegerField(required=True)
class Meta:
model = Student
fields = ['user', 'course', 'year']

You dont need to redefine fields in the ModelForm if you've already mentioned them in the fields attribute. So your form should look like this -
class SelectCourseYear(forms.ModelForm):
class Meta:
model = Student
fields = ['course', 'year'] # removing user. we'll handle that in view
And we can handle the form with ease in the view -
def step3(request):
user = request.user
if request.method == 'POST':
form = SelectCourseYear(request.POST)
if form.is_valid():
student = form.save(commit=False)
# commit=False tells Django that "Don't send this to database yet.
# I have more things I want to do with it."
student.user = request.user # Set the user object here
student.save() # Now you can send it to DB
return render_to_response("registration/complete.html", RequestContext(request))
else:
form = SelectCourseYear()
return render(request, 'registration/step3.html',)

course has to be an instance of a Course model, not just the primary key of the instance. You can still accept an id in the form as a text input, but you're going to need to retrieve the actual course instance and assign the value.
You'll need to verify that the course id is valid, so putting that code into the clean method isn't a bad idea. Notice also how the course field is excluded here? Otherwise the form will expect it to be present. You also don't need to re-define the year field, as the ModelForm will inherit that field from the Student model.
# forms.py
class SelectCourseYear(forms.ModelForm):
class Meta:
model = Student
exclude = ['user', 'course']
course_id = forms.IntegerField()
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(SelectCourseYear, self).__init__(*args, **kwargs)
def clean_course_id(self):
course_id = self.cleaned_data.get('course_id')
try:
self.course = Course.objects.get(pk=course_id)
except Course.DoesNotExist:
raise forms.ValidationError('Sorry, that course id is not valid.')
return course_id
def save(self, commit=True):
instance = super(SelectCourseYear, self).save(commit=False)
instance.course = self.course
instance.user = self.user
if commit:
instance.save()
return instance
# views.py
def step3(request):
if request.method == 'POST':
form = SelectCourseYear(request.POST or None, user=request.user)
if form.is_valid():
form.save()
return render_to_response("registration/complete.html",
RequestContext(request))
return render(request, 'registration/step3.html',)
Now, when you call .save() on the model, the course field will be assigned an instance of Course

Related

Save data from ChoiceField to database Django

I'm new to Django and I have built a Form that shows a single select field to chose from. The data in the field are calculated on the go by the Form.
I now need, once the data is being submitted, to be save on the database. The only problem is that, for some reason, I got an IntegrityError error NOT NULL constraint failed: manager_playlist.user_id
Below my view, form and model in Django
views.py
def playlist(request):
if not is_user_already_auth_spotify(request):
messages.error(request, "You're not authenticated with Spotify, please authenticate here")
return redirect('/members/account/' + request.user.username)
if request.method == "POST":
form = ChoosePlaylistForm(request.POST, request=request)
if form.is_valid():
form.save()
messages.success(request, "Playlist successfully chosen")
return HttpResponseRedirect('account')
else:
pass
else:
form = ChoosePlaylistForm(request=request)
return render(request, 'show_playlist.html', {"playlist_choose_form": form})
forms.py
class ChoosePlaylistForm(ModelForm):
playlists = forms.ChoiceField(choices=())
class Meta:
model = Playlist
fields = ('playlists',)
def __init__(self, *args, request=None, **kwargs):
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
self.request = request
self.fields['playlists'].choices = self.generate_selection()
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
s_user = spotify.current_user()
u_playlists = spotify.user_playlists(s_user['id'], limit=10)
choices = []
for playlist in u_playlists["items"]:
if playlist["owner"]["id"] == s_user['id']:
playlist_choice = (playlist["id"], playlist["name"])
choices.append(playlist_choice)
else:
pass
return choices
model.py
class Playlist(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
playlists = models.CharField(max_length=50, null=True, blank=True) # playlists are the ids of the playlists
def __str__(self):
return self.playlists
The reason for the error is that when a new Playlist object is created, the user field must not be empty (you did not add null=True, and of course, would not make sense here if you did). Now the form validates because the form does not require the user field, only the playlists field. You have a couple of choices.
Option 1
Add the required field to your form (I haven't tested this, please check the docs!):
class ChoosePlaylistForm(ModelForm):
playlists = forms.ChoiceField(choices=())
class Meta:
model = Playlist
fields = ('playlists', 'user',) # NOTE THE CHANGE HERE
def __init__(self, *args, request=None, **kwargs):
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
self.request = request
self.user = request.user # Add the user to the form
Option 2
Save the form as is using commit=False, then add the missing field before you save your model:
if request.method == "POST":
form = ChoosePlaylistForm(request.POST, request=request)
if form.is_valid():
playlist = form.save(commit=False) # NOTE THE CHANGE HERE
playlist.user = request.user # Add the user to the partial playlist
playlist.save() # Now you can save the playlist
messages.success(request, "Playlist successfully chosen")
return HttpResponseRedirect('account')
Option 3
Add the field when you instantiate the form itself (I'm not sure my syntax is correct here):
form = ChoosePlaylistForm(request.POST, request=request, instance=request.user)
EDIT
Option 3 above does not seem to work. I believe this edit will:
form = ChoosePlaylistForm(request.POST, request=request, initial={'user': request.user})

associate the user with the post Django and MySQL

I am trying to associate the user with the post. I have two models students is for user and sublists is for user posts with a foreign key(author). I am using MySQL database and using forms to store data into them. when my form.author execute in my HTML file it gives me a list of ids for all users in the databse but I am already logged in and i want to post as the logged in user without choosing. If remove it says my form is not valid which make sense since im not inputing for form.author.Since I'm using MySQL, I'm not using the built-in User authentication method, but instead comparing both email and password with the login form input. Spend too much time on this but hard to get around with this one. Any help would be appreciated
my views.py look like this
def addnew(request):
if request.method == 'POST':
form = Sublist(request.POST)
if form.is_valid():
try:
form.save()
messages.success(request, ' Subscirption Saved')
name = sublist.objects.get(name=name)
return render (request, 'subscrap/main.html', {'sublist': name})
except:
pass
else:
messages.success(request, 'Error')
pass
else:
form = Sublist()
return render(request, 'subscrap/addnew.html', {'form': form})
#login_required(login_url='login')
#cache_control(no_cache=True, must_revalidate=True, no_store=True)
def main(request):
return render(request, 'subscrap/main.html')
def mod(request):
student = students.objects.all()
return render(request, 'subscrap/mod.html' , {'students': student})
My Models.py
class students(models.Model):
fname = models.CharField(max_length=50)
lname = models.CharField(max_length=50)
password = models.CharField(max_length = 50 , null = True)
passwordrepeat = models.CharField(max_length = 50, null = True)
email = models.EmailField(max_length=150)
class Meta:
db_table = "students"
class sublist(models.Model):
author = models.ForeignKey(students, related_name='sublist' ,on_delete=models.CASCADE)
name = models.CharField(max_length=150)
cost = models.IntegerField(default = 0)
renewalcycle = models.IntegerField(default = 0)
class Meta:
db_table = "sublist"
Since I'm using forms here's my forms.py
lass StudentForm(forms.ModelForm):
class Meta:
model = students
fields = "__all__"
class Studentlogin(forms.Form):
email = forms.EmailField(max_length=150)
password = forms.CharField(max_length = 50, widget=forms.PasswordInput)
class Sublist(forms.ModelForm):
class Meta:
model = sublist
fields = "__all__"
Exclude the Author from the Sublist form:
class Sublist(forms.ModelForm):
class Meta:
model = sublist
exclude = ['author']
In the addnew method, you associate the .instance.author with the request.user:
#login_required(login_url='login')
def addnew(request):
if request.method == 'POST':
form = Sublist(request.POST)
if form.is_valid():
form.instance.author = request.user
form.save()
messages.success(request, ' Subscirption Saved')
return redirect('some_view')
else:
messages.error(request, 'Error')
else:
form = Sublist()
return render(request, 'subscrap/addnew.html', {'form': form})
Note: Models in Django are written in PascalCase, not snake_case,
so you might want to rename the model from sublist to Sublist.
Note: Usually a Form or a ModelForm ends with a …Form suffix,
to avoid collisions with the name of the model, and to make it clear that we are
working with a form. Therefore it might be better to use SublistForm instead of
Sublist.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the students directly. For more information you can see the referencing the User model section of the documentation.

Django ModelForm: Defining a value not passed into the template

I have a ModelForm, and I want to only pass some of the fields into the template. I would like to save one particular field to define after the POST request has been sent. Here is the ModelForm:
class CreateListingForm(ModelForm):
class Meta:
model = models.ListingModel
fields = ['name', 'image', 'description', 'price', 'category']
widgets = {
'description': Textarea()
}
And here is the Model:
class ListingModel(models.Model):
name = models.CharField(max_length=30)
image = models.ImageField(upload_to='images')
description = models.CharField(max_length=1000)
price = models.PositiveIntegerField()
category = models.CharField(max_length=15)
objects = models.Manager()
owner = models.CharField(max_length=100)
In the next code block, I am attempting to define the owner field according to the current user logged in (request.user.username):
#login_required(redirect_field_name=login_view)
def create_listing(request):
if request.method == "GET":
return render(request, "auctions/createlisting.html", {
"CreateListingForm": forms.CreateListingForm()
})
elif request.method == "POST":
form = forms.CreateListingForm(request.POST, request.FILES)
if form.is_valid():
try:
form.owner = request.user.username
print(form.owner)
form.save(commit=True)
except Exception:
return HttpResponseRedirect(reverse("create_listing_error"))
return HttpResponseRedirect(reverse("index")) #TODO
Now, when I say print(form.owner), the result is correct. However when I save the ModelForm, the owner field is left blank. Am I not defining the value of the owner field correctly?
You should not confuse the ModelForm with the instance it is wrapping. The fact that it prints something for form.owner is not that strange, you first set an attribute named .owner, an attribute that did not exists before. You should set the .owner of the .instance of the form:
#login_required(redirect_field_name=login_view)
def create_listing(request):
if request.method == 'POST':
form = forms.CreateListingForm(request.POST, request.FILES)
if form.is_valid():
form.instance.owner = request.user.username
form.save()
return redirect('name-of-some-view')
else:
form = forms.CreateListingForm()
return render(request, 'auctions/createlisting.html', {
'CreateListingForm': form
})
Where 'name-of-some-view' should be replaced by the name of some view to which you redirect in case the form was valid.
You should however consider changing the CharField of owner to a ForeignKey [Django-doc]. Imagine that later the user changes their username, then your ListingModels do no longer refer to a real user.

Post additional model values to database in views.py not modelform

model
class Person(models.Model):
first_name = models.CharField(blank=False,max_length=256,default='')
last_name = models.CharField(blank=False,max_length=256,default='')
plan = models.CharField(blank=False,max_length=256,default='')
plan_price = models.CharField(blank=False,max_length=256,default='')
Views.py
if request.method == 'POST':
if form.is_valid():
form.save(commit=True)
return index(request)
In my modelForm I accept 3 values from the user: first_name, last_name, and plan. I dont have any problem with posting to the database from the form, what i am trying to find out is how I can say something like this
if plan = 'plan1':
#set plan_price to '$399'
else
#set plan_price to '$699'
#then post first_name, last_name, plan, plan_price to database
You can try the following:
if form.is_valid():
person = form.save(commit=False)
plans = {
'plan1': 399,
'plan2': 699,
# ...
}
person.plan_price = plans.get(person.plan, some_default)
person.save()
return index(request)
# you might consider a redirect instead so as not to have the same content on various urls
If the plan price always should match the plan you can also override the model's save method and leave the view as you had it:
class Person(models.Model):
# ...
def save(self, *args, **kwargs):
self.plan_price = some_logic(self.plan)
super(Person, self).save(*args, **kwargs)
But then, you could replace that field by a property alltogether as it seems redundant. If plans, for instance, change prices, I would consider a Plan model with a name and price field.
in your createview you can use this function and write your code there
def form_valid(self, form):
if self.object.plan = 'plan1':
form.instance.price = 399
else:
[...]
return super(your_class_name, self).form_valid(form)
you can access the created object fields by self.object.filed

Using request.user with Django ModelForm

I'm having a problem with logged users and a Django ModelForm. I have a class named _Animal_ that has a ForeignKey to User and some data related to the animal like age, race, and so on.
A user can add Animals to the db and I have to track the author of each animal, so I need to add the request.user that is logged when the user creates an animal instance.
models.py
class Animal(models.Model):
name = models.CharField(max_length=300)
age = models.PositiveSmallIntegerField()
race = models.ForeignKey(Race)
...
publisher = models.ForeignKey(User)
def __unicode__(self):
return self.name
class AnimalForm(ModelForm):
class Meta:
model = Animal
The main goal is hide the publisher field in the form, and submit the logged user when hitting save button.
I can catch the current user in the view using initial, but what I also want is not display the field.
views.py
#login_required
def new_animal(request):
if request.method == "POST":
form = AnimalForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
variables = RequestContext(request, {'form': form})
return render_to_response('web/animal_form.html', variables)
else:
form = AnimalForm(initial={'publisher': request.user})
variables = RequestContext(request, {'form': form})
return render_to_response('web/animal_form.html', variables)
You just need to exclude it from the form, then set it in the view.
class AnimalForm(ModelForm):
class Meta:
model = Animal
exclude = ('publisher',)
... and in the view:
form = AnimalForm(request.POST)
if form.is_valid():
animal = form.save(commit=False)
animal.publisher = request.user
animal.save()
(Note also that the first else clause - the lines immediately following the redirect - is unnecessary. If you leave it out, execution will fall through to the two lines at the end of the view, which are identical.)
Another way (slightly shorter):
You need to exclude the field as well:
class AnimalForm(ModelForm):
class Meta:
model = Animal
exclude = ('publisher',)
then in the view:
animal = Animal(publisher=request.user)
form = AnimalForm(request.POST, instance=animal)
if form.is_valid():
animal.save()
I would add it directly to the form:
class AnimalForm(ModelForm):
class Meta:
model = Animal
exclude = ('publisher',)
def save(self, commit=True):
self.instance.publisher = self.request.user
return super().save(commit=commit)
This is in my opinion the cleanest version and you may use the form in different views.
If you are using ModelAdmin
you should add method get form on your ModelAdmin
class BlogPostAdmin(admin.ModelAdmin):
form = BlogPostForm
def get_form(self, request, **kwargs):
form = super(BlogPostAdmin, self).get_form(request, **kwargs)
form.request = request
return from
and you can now access request in your ModelForm
class ProductAdminForm(forms.ModelForm):
def save(self, commit: bool, *args, **kwargs):
self.instance.user = self.request.user
return super().save(commit=commit)
pass

Categories