Django: Set initial value for ForiegnKey in a CreateView - python

I'm trying to create a view of type CreateView. This view will take the form_class = CourseForm that I created and excluded some fields in it. The instructor field is a foriegnKey and I don't want the user to be able to control it in the form. It's a field that depends on the signed in user.
# forms.py
class CourseForm(forms.ModelForm):
class Meta:
model = Course
exclude = ['instructor', 'members', 'slug']
# ...
my view is as follows. I thought that by including the instructor value in initial would pass the profile instance when I submit
# views.py
#method_decorator(login_required, name='dispatch')
class CourseCreateView(CreateView):
model = Course
template_name = 'course_form.html'
success_url = reverse_lazy('course-create-complete')
form_class = CourseForm
def get_initial(self):
initial = super(CourseCreateView, self).get_initial()
initial = initial.copy()
profile = get_object_or_404(Profile, user__username=self.request.user)
initial['instructor'] = profile
return initial
# models.py
class Course(models.Model):
instructor = models.ForeignKey(Profile, related_name="Instructor")
# ... other fields
but the probelm is that whenever I submit the form I get the following error:
NOT NULL constraint failed: myapp_course.instructor_id

If you want to set the initial value of instructor field, you shouldn't exclude it from the form. You could instead make the field hidden.
Or you could include that in the exclude list, but then you shouldn't override get_initial method, but do the assignment manually:
class CourseCreateView(CreateView):
def form_valid(self, form):
self.object = form.save(commit=False)
# create instructor based on self.request.user
self.object.instructor = instructor
self.object.save()
return HttpResponseRedirect(self.get_success_url())
Check django doc about what does save(commit=False) do.
Also check django doc about form_valid function and how forms are handled in class based views.

Related

How to set Foreign key to custom user model in CreateView?

I have this custom user model 'es_user'
models.py
class es_user(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
class es_event(models.Model):
ev_name = models.CharField(max_length=100)
ev_venue = models.CharField(max_length=100)
ev_admin = models.ForeignKey('es_user',related_name='events',on_delete=None)
Eventually, I'll be adding more fields to es_user that's why I used a custom user model. So I cannot settle for django's inbuilt user model.
views.py
class CreateEventView(LoginRequiredMixin,CreateView):
model = es_event
fields = ['ev_name','ev_venue','ev_date','ev_description']
def form_valid(self, form):
form.instance.ev_admin = self.request.user
return super(CreateEventView, self).form_valid(form)
when I submit the form I get this error
Cannot assign "<SimpleLazyObject: <User: randy>>": "es_event.ev_admin" must be a "es_user" instance.
I've checked Django documentation and other stack overflow posts, but in all those the foreign key is referencing Django's inbuilt user model
So just assign the es_user, not the auth user.
form.instance.ev_admin = self.request.user.es_user
You can fetch the es_user instead:
form.instance.ev_admin = es_user.objects.get(user=self.request.user)
everything worked fine when I made some changes to views.py and models.py
views.py
class CreateEventView(LoginRequiredMixin,CreateView):
model = es_event
fields = ['ev_name','ev_venue','ev_date','ev_description']
def form_valid(self, form):
form.instance.ev_admin = self.request.user.es_user
models.py
class es_user(models.Model):
user = models.OneToOneField(User,related_name='es_user', on_delete=models.CASCADE)

Can't update User and UserProfile in one View?

I use UpdateView to update user account. User consists of User and UserProfile like this:
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE,related_name='userprofile')
telephone = models.CharField(max_length=40,null=True)
Now, I've created a class UpdateView to be able to update for example UserProfile - telephone which works.
FORM:
class UserProfileUpdateForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('telephone',)
URLS:
url(r'^edit-profile$',view=views.UserUpdate.as_view(),name='user_update'),
VIEW:
# #login_required
class UserUpdate(UpdateView):
form_class = UserProfileUpdateForm
context_object_name = 'user_update'
template_name = 'auth/profiles/edit-profile.html'
success_url = 'success url'
def get_object(self,queryset=None):
return self.request.user.userprofile
def form_valid(self, form):
#save cleaned post data
clean = form.cleaned_data
self.object = form.save()
return super(UserUpdate, self).form_valid(form)
Now, I want to be able to change some attributes which belongs to User and some attributes which belongs to UserProfile.
I've already tried to change UserProfileUpdateForm fields variable but It does not work at all...
class UserProfileUpdateForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('telephone','model.user.first_name',) <- this does not work, I want to add to the form attribute 'first_name' which belongs to User, not UserProfile
Do you know what to do to be able to change telephone, first_name, last_name etc. using UpdateView?
UpdateView is only made to handle one model with no relations. However, the wonderful django-extra-views library provides CBVs for models and inline relations.
class UserProfileInline(InlineFormSet):
model = models.UserProfile
form = UserProfileUpdateForm
extra = 0
def get_factory_kwargs(self):
kwargs = super(UserProfileInline,self).get_factory_kwargs()
kwargs.update({"min_num": 1})
return kwargs
class UserCreate(CreateWithInlinesView):
model=User
inlines = [UserProfileInline]
form_class = UserForm
success_url = reverse('some-success-url')
# REQUIRED - fields or exclude fields of User model
template_name = 'your_app/user_profile_update.html'
Be sure to check out the documentation for information on the variables passed to your template and how to work with inline formsets.
You have to create second form for User as well. Then pass it to the same UpdateView as a second form_class.
Note*: you may need to override get and post methods for UpdateView. This SO answer might help.
Render both forms in one template under one <form> tag:
<form action="" method="post">
{% csrf_token %}
{{ first_form }}
{{ second_form }}
</form>

Setting current user on django vanilla CreateView

I would like to update my model with the currently logged in user. I am using django-vanilla-views. To store a new record I am trying to use CreateView. I don't want to display user on the form, just update it automatically.
Here is my model:
class Measurement(models.Model):
date = models.DateField()
user = models.ForeignKey(User)
And here is my view:
class CreateMeasurement(CreateView):
model = Measurement
fields = ['date']
success_url = reverse_lazy('list_measurements')
def get_form(self, data=None, files=None, **kwargs):
kwargs['user'] = self.request.user
return super(CreateMeasurement, self).get_form(data=data, files=files, **kwargs)
Unfortunately when accessing the view I get the following exception:
TypeError: __init__() got an unexpected keyword argument 'user'
I also tried to create a ModelForm for my model but got exactly the same error. Any ideas what I might be doing wrong?
You don't need to pass the user to the form, so don't override the get_form method. You have already excluded the user field from the model form by setting fields in your view, so you shouldn't need a custom model form either.
It should be enough to override the form_valid method, and set the user when the form is saved.
from django.http import HttpResponseRedirect
class CreateMeasurement(CreateView):
model = Measurement
fields = ['date']
success_url = reverse_lazy('list_measurements')
def form_valid(self, form):
obj = form.save(commit=False)
obj.user = self.request.user
obj.save()
return HttpResponseRedirect(self.get_success_url())

Using current user to initialize ForeignKey user in Django CreateView

I'm getting an error:
AttributeError at /courses/create/
'CourseStudentForm' object has no attribute 'user'
When I try to create a new object by setting it's user field to the current user:
class CourseStudentCreate(CreateView):
model = CourseStudent
fields = ['semester', 'block', 'course', 'grade']
success_url = reverse_lazy('quests:quests')
#method_decorator(login_required)
def form_valid(self, form):
data = form.save(commit=False)
data.user = self.request.user
data.save()
return super(CourseStudentCreate, self).form_valid(form)
This is the model:
class CourseStudent(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
semester = models.ForeignKey(Semester)
block = models.ForeignKey(Block)
course = models.ForeignKey(Course)
grade = models.PositiveIntegerField()
active = models.BooleanField(default=True)
The form displays correctly, but when I submit I get the error.
ANSWER:
From here:
Pass current user to initial for CreateView in Django
If I want to keep user as a required field, it works if I change form_valid to:
def form_valid(self, form):
form.instance.user = self.request.user
return super(CourseStudentCreate, self).form_valid(form)
The cause of the error is described by Burhan Khalid below.
The reason it doesn't work is because you are missing a required field from your form class; recall that model form validation will also validate the model instance:
Validation on a ModelForm
There are two main steps involved in validating a ModelForm:
Validating the form
Validating the model instance
In your class, the inherited post method is calling is_valid():
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
You can see that it only calls form_valid() if is_valid() returns true; in your case it can't return true because you have a required attribute missing.
You can solve this problem easily by making the user foreign key optional in your model.

Pass object to modelForm before CreateView form_valid method

I have a model with good validation, I'm using the clean method inside the model. the problem is when I am validating I am using an object that has not been set in the form which raise an exception that the object is not there yet.
I want a solution to pass the object from url primary key to the form before any validation, so my clean method works fine.
Here is a similar example.
The main model
class Student(models.Model):
first_name = models.CharField(max_length=30)
lets sat that each student might have one semester at a time. However, if there are any semesters before then the start date must be after the last semester end date.
class Semester(models.Model):
student = models.OneToOneField(Student)
start_date = models.DateField()
def clean(self):
# do not allow the start date to be before last semester end date
if self.student.semesterhistory_set.all().count() > 0:
last_semester_end_date = self.student.semesterhistory_set.last().end_date
if last_semester_end_date >= self.start_date:
message = _("Start Date for this semester must be after %s" % last_date)
raise ValidationError(message)
class SemesterHistory(models.Model):
student = models.ForeignKey(Student)
start_date = models.DateField()
end_date = models.DateField()
In the view, I am passing the student object which will be used in validation after validating the form. (problem)
# URL for this is like this student/(pk)/semesters/create/
class SemesterCreate(CreateView):
model = Semester
fields = ['start_date']
def form_valid(self, form):
form.instance.student = get_object_or_404(Student, id=int(self.kwargs['pk']))
return super(SemesterCreate, self).form_valid(form)
Error:
RelatedObjectDoesNotExist Semester has no student
Obviously you need call form.save(commit=False) which returns instance ... Also semantically wrong approach raise 404 in form_valid...
class SemesterCreate(CreateView):
model = Semester
fields = ['start_date']
student = object = None
def dispatch(self, request, *args, **kwargs):
self.student = get_object_or_404(Student, id=kwargs['pk'])
return super(SemesterCreate, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.student = self.student
self.object.save()
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('...')
https://docs.djangoproject.com/en/1.8/topics/forms/modelforms/#the-save-method
Actually to have a clean set I would add a custom ModelForm. I also use CreateView and this is how i use it .
First add a custom ModelForm (I personnaly add a forms.py file in my apps):
from django.contrib.auth.forms import UserCreationForm
from .model import Semester
class CreateSemesterForm(UserCreationForm):
error_messages = {
'last_date': _("some message"),
}
class Meta:
model = Semester
fields = ('some', 'fields') #or __all__
def clean_your_date_field(self):
#clean_name_field will be called when the form_valid will be called with all clean_fields functions
#here you define your clean method and raise Validation error
return field
def save(self, commit=True):
semester = super(CreateSemesterForm, self).save(commit=False)
#here you can set some other values
if commit:
semester.save()
return semester
And in your custom CreateView you have to add :
class SemesterCreate(CreateView):
form_class = CreateArtistForm
As you set model and fields in the ModelForm you can remove fields and model args from CreateView.
You also can override form_valid in your Custom ModelForm.
Now CreateView will call form_valid which call all clean functions, and if it's all passes, it returns and save your semester.
I came across this yesterday after facing the exact same issue with my project.
It's been a couple of years since you've posted this, but figure I'd post my solution to help anyone else out who might stumble across this.
The solution I came across is to use a custom modelform:
from django import forms
from .models import Blade
class SemesterForm(forms.ModelForm):
class Meta:
model = Semester
fields = '__all__'
widgets = {'student': forms.HiddenInput()}
And the in your view:
class SemesterCreate(CreateView):
model = Semester
def get_initial(self, **kwargs):
# get current student from pk in url
current_student = get_object_or_404(Student,
pk=self.kwargs.get('pk'))
return { 'site': current_student }
The trick here is that you must set the student field to hidden in the form. That way, it will keep the initialised value you give it in the view, but won't be available to the user.
So, it will be there when the form is submitted and the full_clean() method is called (which will then call the clean() method on the model), and your nice and tidy validations performed in the model clean() will work.

Categories

Resources