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
Related
I have a form in my Django project. In this form I can assign person.
This is my form:
class AssignForm(forms.ModelForm):
user = forms.ModelChoiceField(
queryset=UserProfile.objects.filter(is_active=True)
label=_(u'User')
)
class Meta:
model = Customer
fields = ('user',)
I want to add another filter in this form. It is company. I get a list of all users in this form but I want to just listing the users that belongs to current user's company.
So it should be :
queryset=UserProfile.objects.filter(is_active=True, company = current_user.company)
But I cannot get requests from forms. How can I handle it?
You override the constructor of the form with:
class AssignForm(forms.ModelForm):
user = forms.ModelChoiceField(
queryset=UserProfile.objects.filter(is_active=True)
label=_(u'User')
)
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
if user is not None:
self.fields['user'].queryset = UserProfile.objects.filter(
is_active=True, company__userprofile=user
)
class Meta:
model = Customer
fields = ('user',)
Then in the view you pass the logged in user:
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
if request.method == 'POST':
form = AssignForm(request.POST, request.FILES, user=request.user)
# …
else:
form = AssignForm(user=request.user)
# …
You have to pass the request.user from views.py in order to get it in the form. In your form write this __init__ function and pass the user from your views.py and you can get the user. You can access the user as self.user in your form
class AssignForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.get('user')
kwargs.pop('user')
In your views.py call the form like below
form = AssignForm(user=request.user)
I have the following model in Django which I use to store data about medicines.
class Medicine(models.Model):
Medicine_Name = models.CharField(max_length=100)
User_Associated = models.ForeignKey(User, on_delete=models.CASCADE)
Tablets_In_Box = models.IntegerField()
Dose_in_mg = models.IntegerField()
Dose_Tablets = models.IntegerField()
Number_Of_Boxes = models.IntegerField()
Last_Collected = models.DateField()
def __str__(self):
return self.Medicine_Name
def get_absolute_url(self):
return reverse('tracker-home')
I am trying to create a model form where a user can update the last collection of one of their medicines. Here is what I began with.
class CollectionForm(forms.ModelForm):
class Meta:
model = Medicine
fields = ['Medicine_Name', 'Number_Of_Boxes', 'Last_Collected']
I do not understand how I can call an instance of my model based on the 'Medicine_Name' from the field. In other words, I need the user to be able to select the correct medicine from a dropdown menu, and then the form must update the 'Last_Collected', and 'Numer_Of_Boxes' fields on my Medicine model.
https://docs.djangoproject.com/en/2.1/topics/forms/modelforms/#the-save-method
It seems this contains relevant information, but I struggle to see how to use it in this instance. How can I correctly get the instance of the medicine form I need, based on the user input in the form? Furthermore how can I use the save method in my views to make sure the database gets updated correctly?
EDIT Added view for the form:
def update(request, pk):
instance = Medicine.objects.get(id=pk)
if request.method == 'POST':
form = CollectionForm(user=request.user, instance=instance, data=request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.User_Associated = request.user
instance.save()
else:
form = CollectionForm()
context = {'form': form}
return render(request, 'tracker/medicine_collection.html', context )
**EDIT
views:
def update(request, pk):
instance = Medicine.objects.get(id=pk)
if request.method == 'POST':
form = CollectionForm(instance=instance, data=request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.User_Associated = request.user
instance.save()
return redirect ('/')
....
This is based on updating the instance of the specific user. This tutorial helpt me achieve the same thing.
https://youtu.be/EX6Tt-ZW0so
Tried a different approach (class based views - UpdateView) I just learned here on SO. Did not test it but I think its a step in the right direction.
class UpdateMedicine(LoginRequiredMixin, UpdateView):
model = Medicine #call the model you need to update
fields = ['Medicine_Name', 'Number_Of_Boxes', 'Last_Collected'] #specify the fields you need to update
template_name_suffix = 'medicine_update_form' #specify the template where the update form is living
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(
user=self.request.user, #get the current logged in user
instance=get_object_or_404(Medicine, pk=self.kwargs['pk']) #get the pk of the instance
)
return context
def form_valid(self, form):
form.instance.medicine = get_object_or_404(Medicine, slug=self.kwargs['pk'])
return super().form_valid(form) #saves the updates to the instance
def get_success_url(self):
return reverse('medicine-collection') #name of the url where your 'tracker/medicine_collection.html is living
Link the appropriate templates and urls to the above example and try some things yourself.
Link to the django docs:
https://docs.djangoproject.com/en/3.0/ref/class-based-views/generic-editing/
Good luck!
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
In the admin panel, I can add Persons to my CompleteClass model. There is a M2M relationship between CompleteClass and Person. But, my form doesn't work as it should. The pub_date will update, and I can save the head_count, but not the ModelMultipleChoiceField (persons) -- it will not save.
models.py
class Person(models.Model):
name = models.CharField(max_length=255)
persona_description = models.CharField(max_length=255)
def __str__(self):
return self.name
class CompleteClass(models.Model):
persons = models.ManyToManyField(Person)
class_name = models.CharField(max_length=255)
class_head_count = models.IntegerField()
class_pub_date = models.DateField()
def __str__(self):
return '%s %s' % (self.class_name, self.class_head_count)
def save_complete_class(self):
self.class_pub_date = timezone.now()
self.save()
class Meta:
ordering = ('class_pub_date',)
Here is views.py:
def class_new(request):
if request.method == "POST":
form = CompleteClassForm(request.POST)
if form.is_valid():
complete_class = form.save(commit=False)
complete_class.class_pub_date = timezone.now()
complete_class.save()
form.save_m2m()
return redirect('class_detail', pk=complete_class.pk)
else:
form = CompleteClassForm()
return render(request, 'app/class_edit.html', {'form': form})
and forms.py
class CompleteClassForm(forms.ModelForm):
class Meta:
model = CompleteClass
fields = ('class_name', 'class_head_count',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(CompleteClassForm, self).__init__(*args, **kwargs)
self.fields['class_persons']=forms.ModelMultipleChoiceField(queryset=Person.objects.all())
I've read through the documentation and used the save_m2m since i've set commit=false.
The POST data contains person data, but it's not being written to the database. I'm stumped. Please help!
Only fields named in the fields tuple are saved to the instance. You don't have your m2m field listed there.
You also define your modelchoicefield with a different name - class_persons instead of persons. In fact, there is no reason to define that field separately at all - you haven't changed any of the attributes from the defaults.
And once you've removed that definition, there is also no reason to override __init__, seeing as you never pass the user parameter nor do you use it anywhere in the form.
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