Saving to ManyToManyFields using ModelForm and ModelMultipleChoiceField - python

I've created a basic Django app that contains books/authors/publishers as per the Django Book - trying to use a ModelForm to create a means to modify existing books - the problem is that the 'authors' field is a ManyToManyField and when I choose a choice on the ModelForm it simply wipes the existing selection and doesn't save the new one?
models.py
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField2(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
def __unicode__(self):
return self.title
class BookForm(ModelForm):
class Meta:
model = Book
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all(), required=False)
views.py
def editBook(request, b=None):
instance = None
if b is not None:
instance = Book.objects.get(title=b)
if request.method == 'POST':
form = BookForm(request.POST, instance=instance)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/thanks/')
else:
form = BookForm(instance=instance)
return render_to_response('book_form.html', {'form':form})
Cheers!
edit
I have just found several articles that encourage the following in views.py
authors = form.save(commit=False)
authors.user = request.user
authors.save()
form.save_m2m()
But still not having any luck - can't be this hard!

Solution was to override the save method in the ModelForm:
def save(self, commit=True):
authors =[]
for a in self.cleaned_data['authors']:
authors.append(Author.objects.get(first_name=t.first_name))
b = super(BookForm, self).save(commit=commit)
for a in authors:
b.authors.add(a)
b.save()

Because you override authors field. Either don't do it and let Django do it's job, or handle it manually.

Related

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 using a modelform to update an instance of model

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!

Django - Trying to pass an instance of a model field to a form field

I'm going to do my best not to sound like a real dummy, but no promises. I am a paramedic and I'm trying to make an app to document unit checks electronically.I have a model field that is foreign keyed to a few other models in my project. This field designates the unit the user is staffing for that day. I want the user to choose the unit he/she is staffing that day and have that information auto-fill any forms filled out for that session. I've tried storing the object using sessions and I get this "Object of type 'MedicUnit' is not JSON serializable". I've used the model_to_dict method and tried to pass the string of the unit name through the form_valid method but I get this "Cannot assign "'Medic 2'": "DailyCheck.medic_unit_number" must be a "MedicUnit" instance." I'm relatively new to programming and Django and this seems like a very easy problem to fix, but my google searching skills are not getting me anywhere. Here is my code:
Model.py for the origin of the unit_name model field
class MedicUnit(models.Model):
unit_name = models.CharField(max_length=50, default='')
is_active = models.BooleanField(default=True)
def __str__(self):
return self.unit_name
Model.py for one of the foreign key references to the unit_name
class DailyCheck(models.Model):
daily_user = models.ForeignKey(User, on_delete=models.PROTECT)
record_date = models.DateTimeField(auto_now=True)
medic_unit_number = models.ForeignKey('components.MedicUnit', related_name='medic_unit_number', on_delete=models.PROTECT, default='')
unit_property_number = models.ForeignKey('components.Vehicle', related_name='unit_property_number', on_delete=models.PROTECT, default='')
mileage = models.IntegerField(default=0)
narc_seal_number = models.IntegerField(default=0)
emergency_lights = models.BooleanField()
driving_lights = models.BooleanField()
red_bag = models.BooleanField()
LP_15 = models.BooleanField()
BLS_bag = models.BooleanField()
RTF_bag = models.BooleanField()
suction = models.BooleanField()
oxygen = models.BooleanField()
free_text = models.TextField(default='')
views.py for the directly above model
def check_home_view(request):
if request.method == 'POST':
form = ChooseMedicUnit(request.POST or None)
if form.is_valid():
unit_name = form.cleaned_data.get('medic_unit_number')
request.session['unit_name'] = model_to_dict(unit_name)
print(request.session['unit_name'])
return redirect('daily')
else:
form = ChooseMedicUnit()
return render(request, 'checks/checks_home.html', {'form':form})
class checkAdd(CreateView):
model = DailyCheck
fields = ['unit_property_number', 'mileage', 'narc_seal_number', 'emergency_lights', 'driving_lights', 'red_bag', 'LP_15', 'BLS_bag', 'RTF_bag', 'suction', 'oxygen', 'free_text']
success_url = '/checks'
def form_valid(self, form):
form.instance.daily_user = self.request.user
form.instance.medic_unit_number = self.request.session['unit_name']['unit_name']
return super().form_valid(form)
forms.py
class ChooseMedicUnit(forms.ModelForm):
class Meta:
model = DailyCheck
fields = ['medic_unit_number']
I think you can use MedicUnit.id. This should be sufficient to resolve the issue of initializing the field from the session in other forms:
def check_home_view(request):
if request.method == 'POST':
form = ChooseMedicUnit(request.POST or None)
if form.is_valid():
request.session['unit_name'] = form.cleaned_data.get('medic_unit_number').id # see here
print(request.session['unit_name'])
return redirect('daily')
else:
form = ChooseMedicUnit()
return render(request, 'checks/checks_home.html', {'form':form})
Thank you so much for the answer Andrey. I will try that too. I found that all I had to do was import the MedicUnit model to my view and change my form_valid method to the following:
def form_valid(self, form):
form.instance.daily_user = self.request.user
form.instance.medic_unit_number = MedicUnit.ojbects.get(pk=self.request.session['unit_name']['id'])
return super().form_valid(form)
Apparently sessions cannot store objects since after Django 1.5 I think. Someone may have to fact check me on that. So I referenced an instance of the object with a dictionary value from the model_to_dict data stored in the session with the MedicUnit.object.get call.
If you are having the same problem, you can print the session info to the terminal with a print statement just like in my check_home_view function view. I used that info to see what key was necessary to call primary key number.
I will check Andrey's solution later today and see how well that works. It seems a bit cleaner than my solution.

My form with a ModelMultipleChoiceField is not saving data.

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.

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