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!
Related
How can I enable the user to generate only one instance of an object “bet” with a POST method and modify it through a PUT method (for example)
forms.py
class BetForm(forms.ModelForm):
team1_score = forms.IntegerField(min_value=0, max_value=15)
team2_score = forms.IntegerField(min_value=0, max_value=15)
match = forms.ModelChoiceField(queryset=Match.objects.only('id'))
class Meta:
model = Bet
fields = ('team1_score', 'team2_score', 'match')
models.py
class Bet(models.Model):
match = models.ForeignKey(Match, on_delete=models.CASCADE, related_name='+')
team1_score = models.PositiveIntegerField(default=0)
team2_score = models.PositiveIntegerField(default=0)
def __str__(self):
return (str(self.match))
views.py
def post(self, request):
form = BetForm(request.POST)
if form.is_valid():
form.save()
team1_score = form.cleaned_data.get('team1_score')
team2_score = form.cleaned_data.get('team2_score')
match = form.cleaned_data.get('match')
form = BetForm()
return redirect ('home')
args = {'form': form, 'team1_score': team1_score, 'team2_score': team2_score, 'match': match}
return render(request, self.template_name, args)
Enable the user to generate only one instance of an object “bet”...
For that, you want to add a user field to your Bet model. Here you will save a reference to the user making the request.
class Bet(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL, related_name='bets', blank=True)
match = models.ForeignKey(Match, related_name='bets')
team1_score = models.PositiveIntegerField(default=0)
team2_score = models.PositiveIntegerField(default=0)
class Meta:
unique_together = ('user', 'match')
def __str__(self):
return (str(self.match))
Notice the unique_together option which makes sure a user can only create a single Bet instance for a given match.
modify it through a PUT method (for example)
Django does not automatically parse the body for PUT requests like it does for POST. Browsers normally issue POST request on forms submission. If you still want to solve it using PUT, check this post (pun intended).
Parsing Unsupported Requests (PUT, DELETE, etc.) in Django
My suggestion is to modify your post view so it accepts an optional parameter bet_id. This you can define in urlpatterns. The view would then look like this one. You retrieve the bet if bet_id is provided and pass it to the form. This way it understands the user is modifying it.
def post(self, request, bet_id=None):
if bet_id:
bet = Bet.objects.get(pk=bet_id)
form = BetForm(request.POST, instance=bet)
else:
form = BetForm(request.POST)
if form.is_valid():
bet = form.save(commit=False)
bet.user = request.user
bet.save()
# Do what you want here
Notice that we are not saving the form immediately (commit=False), so we could assign it to a user later on. This user is the logged in user from the request object.
Trying out a very simple test app with viewflow.io using function based views rather than the built-in class based views. The intended idea is that a product is added and then approved (via two different views/ forms). There are two issues I cannot seem to work out:
I want to pass the Product to the approval view (so that the user doing the approval can see the summary of what they are meant to approve. I am not sure how to do this - I tried passing the product_pk via the flow.View in flows.py but this results in an error and if I leave it out then the approval view updates all records rather than the current product.
The flow.If gate in flows.py always seems to be True regardless of whether the approved field in Product has been check or not. Ideally I am hoping that the approval is recorded in the Product model rather than the process model
Probably super basic mistake/ concept I am missing - any help would be appreciated.
In models.py
class Product(models.Model):
name = models.CharField(max_length=30)
quantity = models.IntegerField()
approved = models.BooleanField(default=False)
def __str__(self):
return self.name
class ProductProcess(Process):
product = models.ForeignKey(Product, blank=True, null=True)
def approved(self):
return self.product.approved
class ProductTask(Task):
class Meta:
proxy = True
In flows.py
class ProductFlow(Flow):
process_cls = ProductProcess
task_cls = ProductTask
start = flow.Start(start_process).Next(this.approve)
approve = flow.View(approve_product).Next(this.checkapproval)
checkapproval = flow.If(cond=lambda p: p.approved()) \
.OnFalse(this.approve) \
.OnTrue(this.end)
end = flow.End()
In views.py
#flow_start_view()
def start_process(request, activation):
activation.prepare(request.POST or None,)
form = ProductForm(request.POST or None)
if form.is_valid():
Product.objects.create(
name = form.cleaned_data['name'],
quantity = form.cleaned_data['quantity']
)
activation.done()
return redirect('/test')
return render(request, 'viewflowtest/product.html', {'activation': activation, 'form': form})
#flow_view()
def approve_product(request, activation):
activation.prepare(request.POST or None,)
form = ApproveProductForm(request.POST or None)
if form.is_valid():
Product.objects.update(
approved = form.cleaned_data['approved']
)
activation.done()
return redirect('/test')
return render(request, 'viewflowtest/product.html', {'activation': activation, 'form': form})
The form that is called is a very basic ModelForm class and the URLs are exactly as is described in the demo applications on the project GitHub pages. The template has the {{ activation.management_form }} tag.
First of all, you need to link the product and process. So in start view, you can do
if form.is_valid():
product = Product.objects.create(
name = form.cleaned_data['name'],
quantity = form.cleaned_data['quantity']
)
activation.process.product = product
activation.done()
or even better, if the ProductForm is the ModelForm
if form.is_valid():
product = form.save()
activation.process.product = product
activation.done() # here is new process instance created and saved to db
So the approval view could be rewritten as::
#flow_view()
def approve_product(request, activation):
activation.prepare(request.POST or None,)
form = ApproveProductForm(request.POST or None, instance=activation.process.product)
if form.is_valid():
form.save() # here is the approved field is updated
activation.done()
return redirect('/test')
return render(request, 'viewflowtest/product.html', {'activation': activation, 'form': form})
In addition, you can take a look to the viewflow example with the function-based views - https://github.com/viewflow/cookbook/blob/master/viewflow_customization/customization/parcel/views.py
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
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
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.