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())
Related
I am working on a car rental website for uber drivers in django, from the detailView I need drivers to be able to choose the duration of their rental, and other information will be auto filled to the form from my views.py, i was able to get the driver through request.user, i also need the PK of the car to be rented. searching through here i’ve tried various suggestions by people here, but i keep getting one error after another…
using
self.kwargs['pk'] results in ValueError at /car/offer/4/ Cannot assign "4": "CarRent.car" must be a "Car" instance.
then i tried using
form.car = Car.objects.get(pk= self.kwargs.get('pk')) which results in a AttributeError at /car/offer/4/ 'CarRent' object has no attribute 'is_valid'
can someone please tell me how to get the car instance saved in the CarRent model? any help will be greatly appreciated. Thanks
below is my code (reduced to the relevant bit)
models.py
class Car(models.Model):
car_owner = models.ForeignKey(User, related_name='car_owner', on_delete=models.CASCADE)
class CarRent(models.Model):
car = models.ForeignKey(Car, related_name='rented_car', on_delete=models.CASCADE)
driver = models.ForeignKey(User, related_name='driver_renting', on_delete=models.CASCADE)
rented_weeks = models.BigIntegerField(default=1, choices=WEEK_CHOICES)
forms.py
class RentForm(forms.ModelForm):
class Meta:
model = CarRent
fields = ['rented_weeks']
i’m only displaying the rented weeks as that’s the only information i need from the user.
views.py
class CarView(FormMixin, DetailView):
model = Car
form_class = RentForm
def get_success_url(self):
return reverse('car-details', kwargs={'pk': self.object.pk})
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
form = form.save(commit=False)
form.car = self.kwargs['pk']
form.driver = request.user
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.save()
return super().form_valid(form)
form.car expects a Car object, not a string with the primary key, but you can simply use:
from django.contrib.auth.mixins import LoginRequiredMixin
class CarView(LoginRequiredMixin, FormMixin, DetailView):
# …
def post(self, request, *args, **kwargs):
form = self.get_form()
self.object = self.get_object()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.instance.car_id = self.kwargs['pk']
form.instance.driver = self.request.user
form.save()
return super().form_valid(form)
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].
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 User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
I'm using a custom CreateView (CourseCreate) and UpdateView (CourseUpdate) to save and update a Course. I want to take an action when the Course is saved. I will create a new many-to-many relationship between the instructor of the new course and the user (if it doesn't already exist).
So, I want to save the Course as course, and then use course.faculty to create that new relationship. Where is the best place to make this happen?
I'm trying to do this in form_valid in the views, but I'm getting errors when trying to access form.instance.faculty bc the course isn't created yet (in CourseCreate). The error message is like:
"Course: ..." needs to have a value for field "course" before this many-to-many relationship can be used.
It's also not working in CourseUpdate. The Assists relationship is not created. Should I be trying this in the Form? But I'm not sure how to get the user info to the Form.
Thank you.
models.py
class Faculty(models.Model):
last_name = models.CharField(max_length=20)
class Course(models.Model):
class_title = models.CharField(max_length=120)
faculty = models.ManyToManyField(Faculty)
class UserProfile(models.Model):
user = models.OneToOneField(User)
faculty = models.ManyToManyField(Faculty, through='Assists')
class Assists(models.Model):
user = models.ForeignKey(UserProfile)
faculty = models.ForeignKey(Faculty)
views.py
class CourseCreate(CreateView):
model = Course
template_name = 'mcadb/course_form.html'
form_class = CourseForm
def form_valid(self, form):
my_course = form.instance
for f in my_course.faculty.all():
a, created = Assists.objects.get_or_create(user=self.request.user.userprofile, faculty=f)
return super(CourseCreate, self).form_valid(form)
class CourseUpdate(UpdateView):
model = Course
form_class = CourseForm
def form_valid(self, form):
my_course = form.instance
for f in my_course.faculty.all():
a, created = Assists.objects.get_or_create(user=self.request.user.userprofile, faculty=f)
return super(CourseUpdate, self).form_valid(form)
The form_valid() method for CreateView and UpdateView saves the form, then redirects to the success url. It's not possible to do return super(), because you want to do stuff in between the object being saved and the redirect.
The first option is to not call super(), and duplicate the two lines in your view. The advantage of this is that it's very clear what is going on.
def form_valid(self, form):
self.object = form.save()
# do something with self.object
# remember the import: from django.http import HttpResponseRedirect
return HttpResponseRedirect(self.get_success_url())
The second option is to continue to call super(), but don't return the response until after you have updated the relationship. The advantage of this is that you are not duplicating the code in super(), but the disadvantage is that it's not as clear what's going on, unless you are familiar with what super() does.
def form_valid(self, form):
response = super(CourseCreate, self).form_valid(form)
# do something with self.object
return response
I would suggest to use Django's Signal. That is an action that gets triggered when something happens to a Model, like save or update. This way your code stays clean (no business logic in the form-handling), and you are sure that it only gets triggered after save.
#views.py
from django.dispatch import receiver
...
#receiver(post_save, sender=Course)
def post_save_course_dosomething(sender,instance, **kwargs):
the_faculty = instance.faculty
#...etc
If you need to modify also the Course object when call save function use False and after change save the object
def form_valid(self, form):
self.object = form.save(False)
# make change at the object
self.object.save()
return HttpResponseRedirect(self.get_success_url())
It is possible to do return super() as it is in the django doc:
https://docs.djangoproject.com/en/4.0/topics/class-based-views/generic-editing/
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)
I want to connect each post with the logged in user who posted it.
models.py
from django.conf import settings
from django.db import models
# Create your models here.
class Campagin(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
title = models.CharField(max_length=120)
media = models.FileField()
description = models.TextField(max_length=220)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __str__(self):
return self.title`
As you can see the posts were made by two different users, but the relation shows that it is made by the first user
this image shows the registered users..
Views.py
class NewCampagin(LoginRequiredMixin, CreateView):
template_name = 'campagin/new_campagin.html'
model = Campagin
fields = ['title','media','description']
def get_absolute_url(self):
return reverse('campagin:active_campagin')
Okay so CreateView allows you to specify the model and fields attributes to implicitly create a form for you. It's quite neat for quick form submissions but in your case, you will need to make some customizations before saving the Campaign object into the database (linking up the current logged in user).
As a result, you will need to create your own form first (create a file called forms.py which can be next to your views.py) and enter this code:
class CampaignForm(ModelForm): # Import ModelForm too.
def __init__(self, *args, **kwargs):
# We need to get access the currently logged in user so set it as an instance variable of CampaignForm.
self.user = kwargs.pop('user', None)
super(CampaignForm, self).__init__(*args, **kwargs)
class Meta:
model = models.Campaign # you need to import this from your models.py class
fields = ['title','media','description']
def save(self, commit=True):
# This is where we need to insert the currently logged in user into the Campaign instance.
instance = super(CampaignForm, self).save(commit=False)
# Once the all the other attributes are inserted, we just need to insert the current logged in user
# into the instance.
instance.user = self.user
if commit:
instance.save()
return instance
Now that we have our forms.py all ready to go we just need to modify your views.py:
class NewCampagin(LoginRequiredMixin, CreateView):
template_name = 'campagin/new_campagin.html'
form_class = forms.CampaignForm # Again, you'll need to import this carefully from our newly created forms.py
model = models.Campaign # Import this.
queryset = models.Campaign.objects.all()
def get_absolute_url(self):
return reverse('campagin:active_campagin') # Sending user object to the form, to verify which fields to display/remove (depending on group)
def get_form_kwargs(self):
# In order for us to access the current user in CampaignForm, we need to actually pass it accross.
# As such, we do this as shown below.
kwargs = super(NewCampaign, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
What's actually happening with my POST requests under the bonnet??
Note: This is just extra information for the sake of learning. You do
not need to read this part if you don't care about how your class
based view is actually handling your post request.
Essentially CreateView looks like this:
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
"""
View for creating a new object instance,
with a response rendered by template.
"""
template_name_suffix = '_form'
Doesn't look that interesting but if we analyse BaseCreateView:
class BaseCreateView(ModelFormMixin, ProcessFormView):
"""
Base view for creating an new object instance.
Using this base class requires subclassing to provide a response mixin.
"""
def post(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).post(request, *args, **kwargs)
we can see we are inheriting from two very important classes ModelFormMixin and ProcessFormView. Now the line, return super(BaseCreateView, self).post(request, *args, **kwargs), essentially calls the post function in ProcessFormView which looks like this:
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)
As you can see, your CreateView really just boils down to this small post function which simply gets a specified form and validates + saves it. There's 2 questions to ask at this point.
1) What does form = self.get_form() do since I didn't even specify my form?
2) What is self.form_valid(form) actually doing?
To answer the first question, self.get_form() essentially calls another function form_class = self.get_form_class() and this function is actually found in ModelFormMixin (the one where inherited from!):
def get_form_class(self):
"""
Returns the form class to use in this view.
"""
if self.fields is not None and self.form_class:
raise ImproperlyConfigured(
"Specifying both 'fields' and 'form_class' is not permitted."
)
if self.form_class:
return self.form_class
else:
if self.model is not None:
# If a model has been explicitly provided, use it
model = self.model
elif hasattr(self, 'object') and self.object is not None:
# If this view is operating on a single object, use
# the class of that object
model = self.object.__class__
else:
# Try to get a queryset and extract the model class
# from that
model = self.get_queryset().model
if self.fields is None:
raise ImproperlyConfigured(
"Using ModelFormMixin (base class of %s) without "
"the 'fields' attribute is prohibited." % self.__class__.__name__
)
# THIS IS WHERE YOUR FORM WAS BEING IMPLICITLY CREATED.
return model_forms.modelform_factory(model, fields=self.fields)
As you can see, this function is where your form was being implicitly created (see very last line). We needed to add more functionality in your case so we created our own forms.py and specified form_class in the views.py as a result.
To answer the second question, we need to look at the function (self.form_valid(form)) call's source code:
def form_valid(self, form):
"""
If the form is valid, save the associated model.
"""
# THIS IS A CRUCIAL LINE.
# This is where your actual Campaign object is created. We OVERRIDE the save() function call in our forms.py so that you could link up your logged in user to the campaign object before saving.
self.object = form.save()
return super(ModelFormMixin, self).form_valid(form)
So here we are simply saving the object.
I hope this helps you!
More information at https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-editing/#createview
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.
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.