Is there a way I can get the product gotten from product = Product.objects.get(id=id) and use it in my form validation? In my template, I have listed all my products and each has the AddSaleForm. When the user fills the form and submits it sends them to the make_sale view. Now I need to check that the quantity entered from the form does not exceed the quantity each product has (as shown in my commented code in my FORMS.PY.)
Is there a way I can get the product?
MY FORMS.PY
class AddSaleForm(forms.ModelForm):
class Meta:
model = Sale
fields = ['quantity', 'selling_price']
widgets = {
'quantity': NumberInput(attrs={'class': 'form-control', 'placeholder': 'items number'}),
'selling_price': NumberInput(attrs={'class': 'form-control', 'placeholder': '1000.00'}),
}
def clean_quantity(self, *args, **kwargs):
sale_quantity = self.cleaned_data.get('quantity')
if sale_quantity == 0:
raise forms.ValidationError('The sale quantity cannot be zero (0)')
# elif sale_quantity > product_quantity: # <----rom the product accessed from make_sale view
# raise forms.ValidationError('The sale quantity cannot exceed the available quantity. '
# 'The availabe quantity for this product is ' + str(product_quantity))
return sale_quantity
MY VIEWS.PY
def make_sale(request, id):
product = Product.objects.get(id=id) # To be accessed in the form for validation
if request.method == 'POST':
form = AddSaleForm(request.POST)
if form.is_valid():
quantity = form.cleaned_data['quantity']
selling_price = form.cleaned_data['selling_price']
Sale.objects.create(product=product, quantity=quantity, selling_price=selling_price, sold_by=request.user)
product_quantity = product.quantity
sale_no = product.number_of_sales
new_sale = sale_no + 1
new_quantity = product_quantity - quantity
Product.objects.filter(id=product.id).update(quantity=new_quantity, number_of_sales=new_sale)
messages.success(request, str(quantity) + ' ' + 'item(s) sale for ' + str(product.name) + ' at Kshs. '
+ str(selling_price) + ' made successfully!')
return redirect(reverse('products:todays_sales'))
I don't know if this is a good idea to send other object to non related model form but one way is to send Product instance to your form and then use it's attributes in clean method of your AddSaleForm. Something like following code:
Goes in your views ->
product = Product.objects.get(id=id)
form = AddSaleForm(request.POST or None, initial={'product_instance': product})
And then in your clean method of AddSaleForm you have access to that instance. Following code may help you to understand what I'm saying:
Goes in your forms ->
class AddSaleForm(forms.ModelForm):
model = Sale
fields = ['quantity', 'selling_price']
widgets = {
'quantity': NumberInput(attrs={'class': 'form-control', 'placeholder': 'items number'}),
'selling_price': NumberInput(attrs={'class': 'form-control', 'placeholder': '1000.00'}),
}
def clean(self):
super().clean()
product_instance = self.initial['product_instance']
# You can add your conditions based on product instance here
Related
My edit view in my application won't update the court details whenever I want to edit it. I keep getting the error below. Adding, Viewing & Deleting functions are working okay. I cant find a solution on the other S/O answers. Some help on this would be appreciated. Thanks
Exception Value: 'str' object has no attribute 'court_number'
model.py
# Create your models here.
class Court(models.Model):
court_number = models.CharField(max_length=255, verbose_name='Court Number.')
accused_person = models.ForeignKey(AccusedPerson, on_delete=models.CASCADE, verbose_name='Accused Person')
court = models.CharField(choices=COURTS, max_length=255, verbose_name='Court')
court_verdict = models.CharField(choices=VERDICT, max_length=50, verbose_name='Court Status')
scheduled_on = models.DateField(verbose_name='Scheduled On')
created_by = models.ForeignKey(Profile, on_delete=models.CASCADE, verbose_name='Police Officer')
date_created = models.DateTimeField(auto_now_add=True, verbose_name='Date Created')
date_updated = models.DateTimeField(auto_now=True, verbose_name='Date Updated')
def __str__(self):
return str(self.court_number)
class Meta:
verbose_name_plural = 'Court'
forms.py
class EditCourtInfoForm(forms.Form):
court_number = forms.CharField(max_length=50, required=True, widget=forms.TextInput(attrs={'id': 'court_number', 'class': 'form-control mb-4', 'name': 'court_number', 'placeholder': 'Court Number'}))
accused_person = forms.ChoiceField(required=True, widget=forms.Select(attrs={'id': 'accused_person', 'class': 'form-control mb-4', 'name': 'accused_person', 'placeholder': 'Accused Person'}))
court = forms.ChoiceField(choices=COURTS, required=True, widget=forms.Select(attrs={'id': 'court', 'class': 'form-control mb-4', 'name': 'court', 'placeholder': 'Court'}))
court_verdict = forms.ChoiceField(choices=VERDICT, required=True, widget=forms.Select(attrs={'id': 'court_verdict', 'class': 'form-control mb-4', 'name': 'court_verdict', 'placeholder': 'Verdict'}))
scheduled_on = forms.DateField(required=True, widget=forms.DateInput(attrs={'type': 'date', 'id': 'scheduled_on', 'class': 'form-control mb-4', 'name': 'scheduled_on', 'placeholder': 'Scheduled On'}))
def __init__(self, *args, **kwargs):
super(EditCourtInfoForm, self).__init__(*args, **kwargs)
self.fields['accused_person'].choices = [(e.pk, f"{e.first_name}" + ' ' + f"{e.middle_name}" + ' ' + f"{e.last_name}") for e in AccusedPerson.objects.all()]
class Meta:
model = Court
fields = ['court_number', 'accused_person', 'court', 'court_verdict', 'scheduled_on']
views.py
def EditCourtInfo(request, id):
court = Court.objects.get(id=id)
if request.method == 'POST':
form = EditCourtInfoForm(request.POST)
if form.is_valid():
context = {'has_error': False}
court_number = form.cleaned_data['court_number']
accused_person = form.cleaned_data['accused_person']
court = form.cleaned_data['court']
court_verdict = form.cleaned_data['court_verdict']
scheduled_on = form.cleaned_data['scheduled_on']
print(scheduled_on)
court.court_number = court_number # The problem
court.accused_person = AccusedPerson.objects.get(pk=int(accused_person))
court.court = court
court.court_verdict = court_verdict
court.scheduled_on = scheduled_on
court.created_by = request.user.profile
if not context['has_error']:
court.save()
messages.success(request, '✅ Court Record Successfully Updated!')
return redirect('OfficerCourtInfo')
else:
messages.error(request, '⚠️ Court Record Was Not Updated!')
return redirect('EditCourtInfo', id=id)
else:
form = EditCourtInfoForm()
return render(request, 'Officer Edit Court.html', {'form':form, 'court':court})
The immediate problem is that you have
court = Court.objects.get(id=id)
but then later
court = form.cleaned_data['court']
So you are re-using a variable for a different purpose. You could fix this problem by using a different variable for one of these. However, you are making this much more complicated than you need to. The form will already take care of editing the Court object for you:
def EditCourtInfo(request, id):
court = Court.objects.get(id=id)
if request.method == 'POST':
form = EditCourtInfoForm(request.POST, instance=court). # pass the court object to the form
if form.is_valid():
form.save() # just save the form
messages.success(request, '✅ Court Record Successfully Updated!')
return redirect('OfficerCourtInfo')
else:
messages.error(request, '⚠️ Court Record Was Not Updated!')
return redirect('EditCourtInfo', id=id)
else:
form = EditCourtInfoForm()
return render(request, 'Officer Edit Court.html', {'form':form, 'court': court})
You will need to change your form to extend ModelForm instead of Form:
class EditCourtInfoForm(forms.ModelForm):
This code is untested, so I may have missed something. I suggest checking out the Django documentation to fill in any gaps in your understanding. You might even consider using a class-based view instead.
I was looking through this article to figure out how to set a field's value after a form is initialized. I don't see this in Django's docs, or maybe I'm putting in the wrong query, but is there a way to set the 'min' attribute value of a field in views.py? I'm asking because the min value can change constantly since it's a bid amount that is set each time a user bids above the highest_bid variable value in the view_listing function.
models.py
class Bid(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
listing = models.ForeignKey(Listing, on_delete=models.CASCADE, null=True)
bid_amount = models.DecimalField(decimal_places=2, max_digits=10)
def __str__(self):
return f"{self.user}'s bid on {self.listing} is {self.bid_amount}"
forms.py
class BidForm(ModelForm):
class Meta:
model = Bid
fields = ('bid_amount',)
labels = { 'bid_amount': ('Your Bid'), }
widgets = {
'bid_amount': forms.NumberInput(attrs={'step': 00.50, 'class': 'form-control', 'style': 'width:50%', 'min': 0, 'title': '', 'placeholder': '' })
}
views.py
#login_required
def view_listing(request, listing_id):
listing = get_object_or_404(Listing, pk=listing_id)
bids = Bid.objects.filter(listing=listing)
all_bids = [] # Set an empty bid list to get all bids if any exist
highest_bid = listing.starting_price # Set highest bid to 0 in case there are no bids
if bids:
bid_list = list(bids) # Convert Queryset to a list
for index in bid_list:
all_bids.append(index)
highest_bid = all_bids[-1].bid_amount # Get the highest bid from all_bids
listing.starting_price = highest_bid
else:
highest_bid # Else highest bid is 0
if request.method == "GET":
bid_form = BidForm()
# Bid must be at least equal to highest bid
bid_form.initial['bid_amount'] = highest_bid
print(bid_form.initial['bid_amount'])
try: # Check if the item is in the user's watchlist and pass the user's wishlist item name if it does
Watchlist.objects.get(user=request.user, listing=listing)
list_item = Watchlist.objects.get(user=request.user, listing_id=listing_id)
return render(request, "auctions/viewlisting.html" , { "listing": listing, "item": list_item, "bids": bids, "bid_form": bid_form, "highest_bid": highest_bid })
except: # else return None
None
return render(request, "auctions/viewlisting.html" , { "listing": listing, "bids": bids, "bid_form": bid_form, "highest_bid": highest_bid })
else: # On POST, allow a user to bid on an item
try:
bid_form = BidForm(request.POST)
newbid = bid_form.save(commit=False)
newbid.user = request.user
if bid_form.is_valid():
bid_amount = request.POST.get("bid_amount")
# Got bid value from form. Left off here
bid_value = bid_form.cleaned_data['bid_amount']
if bid_value <= highest_bid:
messages.error(request, f"Your bid must be greater than ${highest_bid}")
return HttpResponseRedirect(listing.get_absolute_url())
bid = Bid.objects.create(
listing=listing, user=request.user, bid_amount=bid_amount
)
bid.save()
messages.success(request, "Your bid was saved.")
return HttpResponseRedirect(listing.get_absolute_url())
return redirect("auctions:index")
except ValueError:
messages.error(request, "Your bid was not accepted.")
return render(request, "auctions/viewlisting.html", { "listing": listing, "error": ValueError })
viewlisting.html
some html code...
<!-- my bid form --!>
<form action="" method="POST">
{% csrf_token %} {{ bid_form.as_p }}
<input type="submit" value="Submit Your Bid" class="btn btn-danger">
</form>
In the "GET" section of my views.py file, where bid_form.initial['bid_amount'] = highest_bid is set, I also want to set the bid_form['bid_amount'] min value to highest_bid value each time it changes so that the user cannot enter anything lower than highest_bid in the form field. Is this at all possible to set in the views.py file? I've tried bid_form['bid_amount'].min but this shows up in the console <built-in method min of decimal.Decimal object at 0x7f99172a82d0>
You could make a custom function that checks all the other bid amounts, and returns a validation error if new bidding is less than any previous biddings
def validate_highest_bid(value):
bids = Bid.objects.all()
for bid in bids:
if value <= bid.bid_amout:
raise ValidationError(
f"{value} must be higher than previous value",
params={'value': value},
)
class Bid(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
listing = models.ForeignKey(Listing, on_delete=models.CASCADE, null=True)
bid_amount = models.DecimalField(decimal_places=2, max_digits=10, validators=[validate_highest_bid])
def __str__(self):
return f"{self.user}'s bid on {self.listing} is {self.bid_amount}"
You can then add something on save that handles the error
I am trying to create a reservation form that creates an object for model Reservation when the form is posted. I am using a custom datepicker widget to pick the ate, but I am also using ModelForms.
The issue is that, if I do not have 'date' listed in the meta fields list in the forms.py, then the form doesn't look for the date field form input on post. But if I include 'date' inside the meta fields of the forms.py Modelform, then it errors and says "date field can not be left blank" even though it is not blank...
forms.py
class ReservationForm(forms.ModelForm):
date = forms.DateField(
widget=DatePickerInput(format='%m/%d/%Y')
)
def clean_date(self):
data = self.cleaned_data['date']
# Check if a date is not in the past.
if data < datetime.date.today():
raise ValidationError(_('Invalid date - reservation in past'), code='invalid')
messages.danger(request, "Reservation Created")
print('ERROR')
# Remember to always return the cleaned date.
return data
class Meta:
model = Reservation
fields = ('reservation_time', 'people', 'name', 'email', 'phone') # REMOVED 'date'
views.py
def reservationFormView(request):
#reservation = create(Reservation)
# If this is a POST request then process the Form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = ReservationForm(request.POST)
# Check if the form is valid:
if form.is_valid():
# process the data in form.cleaned_data as required
reservation = form.save(commit=False)
reservation.ReservationEmail = form.cleaned_data['email']
reservation.ReservationName = form.cleaned_data['name']
reservation.ReservationPeople = form.cleaned_data['people']
reservation.ReservationTime = form.cleaned_data['reservation_time']
reservation.date = form.cleaned_data['date']
print( reservation.date)
#reservation.created_time = timezone.now()
reservation.save()
# redirect to a new URL:
return HttpResponseRedirect('/reservation-confirmation/')
# If this is a GET (or any other method) create the default form.
else:
form = ReservationForm()
return render(request, 'home/reservation_form.html', {'form': form, })
models.py
class Reservation(BaseModel):
class Meta:
verbose_name_plural = "Reservations"
TIME_CHOICES = (
...
)
SEATING_CHOICES = (
...
)
date = models.DateField(null=True)
name = models.CharField(max_length=35, null=True)
phone = PhoneNumberField(null=True) #USE THIS https://github.com/stefanfoulis/django-phonenumber-field
email = models.EmailField(null=True)
people = models.PositiveSmallIntegerField(choices=SEATING_CHOICES, default=None, db_index=True)
reservation_time = models.PositiveSmallIntegerField(choices=TIME_CHOICES, default=None, db_index=True)
def __str__(self):
return '(%s) %s %s' % (self.date, self.name, self.phone )
Your clean_date method does not return a value in case the if condition is False. You should return the cleaned data in case it is correct, like:
def clean_date(self):
data = self.cleaned_data['date']
# Check if a date is not in the past.
if data < datetime.date.today():
raise ValidationError(_('Invalid date - reservation in past'), code='invalid')
messages.danger(request, "Reservation Created")
# not indented under the if
return data
Otherwise, this function will return None in case the data is valid, and raise a ValidationError in case the data is invalid.
My view passes an id to my form. This id is a foreign key from another table. I am not able to save the id in the database table.
(id : voucher_id, table in which i am saving the form : TmpPlInvoicedet)
What i want to do
Send voucher_id from (View) to ---> TmpFormDetForm (Form) ---> TmpPlInvoicedet (DB)
Trying to get instance from the table 'TmpPlInvoice' (which has voucher_id as PK) and save it in the form gives me
DoesNotExist at /new/ TmpPlInvoice matching query does not exist
What am i doing wrong?
Views.py
def new_invoic(request):
# Create a voucher id according to my criteria
temp_vid = TmpPlInvoice.objects.order_by().values_list("voucher_id", flat=True).distinct()
if not temp_vid:
voucher_id = str(1).zfill(4)
else:
voucher_id = str(int(max(temp_vid)) + 1).zfill(4)
# POST METHOD TRying to show the voucher_id in the form in readonly format
if request.method == 'POST':
form_pk = TmpForm(request.POST or None, voucher_id=voucher_id,initial={'voucher_id': voucher_id})
if form.is_valid():
form_pk.save()
form = TmpFormDetForm(request.POST or None, voucher=voucher_id, initial={'voucher': voucher_id})
# My assumption is that since i have save the voucher_id in the TmpInvoice table so i can get the PK voucher_id value and save it in the TmpInvoiceDetForm
form.save()
return HttpResponseRedirect('/new/')
else:
return render_to_response('test.html',{'form': form, 'form_pk': form_pk},context_instance=RequestContext(request))
else:
form_pk = TmpForm(voucher_id=voucher_id,initial={'voucher_id': voucher_id})
form = TmpFormDetForm(voucher=voucher_id, initial={'voucher': voucher_id})
return render_to_response('test.html',{'form': form, 'form_pk': form_pk},context_instance=RequestContext(request))
Forms.py
# This form contains the FK. This one is giving errors while saving.
class TmpFormDetForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
voucher = kwargs.pop('voucher', None)
super(TmpFormDetForm, self).__init__(*args, **kwargs)
self.fields['voucher'].initial = TmpPlInvoice.objects.get(voucher_id=voucher)
voucher = forms.CharField(widget=forms.TextInput(attrs={'size':'40'}))
class Meta:
model = TmpPlInvoicedet
exclude = ['emp_id','particulars','qty', 'rate' , 'itemtot', 'stock_code' ]
widgets = {
'voucher': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '', 'required': 'False', 'name': 'voucher','readonly': 'readonly'}),
'lineitem': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Add Total', 'required': 'False', 'blank': 'True'})}
# This form takes the PK. I save the PK here first.
class TmpForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
voucher_id = kwargs.pop('voucher_id', None)
super(TmpFor, self).__init__(*args, **kwargs)
self.fields['voucher_id'].initial = voucher_id
pos_code = MyModelChoiceField(queryset=Positions.objects.all(), widget=forms.Select(attrs={'class': 'select2_single form-control', 'blank': 'True'}))
cust = MyModelChoiceField(queryset=Custodian.objects.all(), to_field_name='acct_id',widget=forms.Select(attrs={'class': 'select2_single form-control', 'blank': 'True'}))
acct = MyModelChoiceField(queryset=Item.objects.all(), to_field_name='stock_code',widget=forms.Select(attrs={'class':'select2_single form-control', 'blank': 'True'}))
voucher_date = forms.DateField(widget=forms.TextInput(attrs={'tabindex': '-1', 'class': 'form-control has-feedback-left', 'id': 'single_cal1','aria-describedby': 'inputSuccess2Status'}))
class Meta:
model = TmpPlInvoice
exclude = ['net_amt', 'post_date', 'address', 'posted']
widgets = {
'voucher_id': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '', 'required':'False', 'name': 'voucher_id', 'readonly': 'readonly'}),
'voucher_date': forms.TextInput(attrs={'tabindex': '-1', 'class': 'form-control has-feedback-left', 'id': 'single_cal1','aria-describedby': 'inputSuccess2Status'}),
'particulars': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Add Particulars', 'required':'False'}),
}
Models.py
class TmpPlInvoicedet(models.Model):
stock_code = models.CharField(max_length=13, blank=True, null=True)
voucher = models.ForeignKey(TmpPlInvoice, db_column='voucher_id')
lineitem = models.CharField(max_length=6)
particulars = models.CharField(max_length=200, blank=True, null=True)
qty = models.FloatField(blank=True, null=True)
rate = models.FloatField(blank=True, null=True)
itemtot = models.FloatField(blank=True, null=True)
emp_id = models.CharField(max_length=8, blank=True, null=True)
class Meta:
managed = False
db_table = 'tmp_pl_invoicedet'
unique_together = (('voucher', 'lineitem'),)
Easy peesy.
def master_detail(request):
def get_new_voucher_id():
temp_vid = TmpPlInvoice.objects.order_by().values_list("voucher_id", flat=True).distinct()
logger.info('Voucher ID already present %s', temp_vid)
if not temp_vid:
voucher_id = str(1).zfill(4)
else:
voucher_id = str(int(max(temp_vid)) + 1).zfill(4)
return voucher_id
voucher_id = get_new_voucher_id()
author_form = TmpForm(initial={'voucher_id': voucher_id})
author = TmpPlInvoice()
BookFormSet = inlineformset_factory(TmpPlInvoice, TmpPlInvoicedet, exclude=('emp_id', 'itemtot', 'voucher', 'lineitem','id'),
form=TmpFormDetForm, extra=1)
formset = BookFormSet(instance=author)
if request.method == 'POST':
logger.info('*'*50)
author = TmpForm(request.POST, initial={'voucher_id': voucher_id})
if author.is_valid():
logger.info('Data for Author is %s', author.cleaned_data)
created_author = author.save()
formset = BookFormSet(request.POST, instance=created_author)
if formset.is_valid():
logger.info('Data for Book is %s', formset.cleaned_data)
formset.save()
else:
logger.info('Formset errors %s', formset.errors)
else:
logger.info('Master form errors %s', author.errors)
logger.info('*'*50)
return HttpResponseRedirect('/new/')
else:
logger.info('Formset from GET is %s', formset.errors)
return render_to_response('new_invoice.html',
{'form': author_form, 'formset': formset},context_instance=RequestContext(request))
You seem to be creating a new invoice ID and then, in your form, attempting to get the invoice matching that ID. But that invoice doesn't exist yet, of course, because you haven't created it.
You might want to use get_or_create to ensure that the invoice is created if it doesn't exist.
For some reason, the exclude in my forms isn't working and the primary key of my Item models is showing up on my formset. How can I get rid of it?
Form:
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ('name',
'description',
'quantity',
'start',
'end',
'cost_price',
'selling_price',)
widgets = {
'cost_price': forms.TextInput(attrs={'onChange':'updateSellingPrice()'}),
'description': forms.Textarea,
'start': SelectDateWidget,
'end': SelectDateWidget}
exclude = ('id')
ItemFormSet = modelformset_factory(Item, form=ItemForm, max_num=5, extra=3, exclude=('id'))
View:
def item_details(request, event_slug, role_id, module_slug):
event = get_object_or_404(Event, slug=event_slug)
payment_details = EventPaymentDetail.objects.get_or_create(event=event)[0]
try:
item_details = Item.objects.filter(event=event)
except:
item_details = Item.objects.get_or_create(event=event)[0]
if request.method == 'POST':
item_formset = ItemFormSet(request.POST)
#display_error(request, item_formset)
if item_formset.is_valid():
instances = item_formset.save(commit=False)
for instance in instances:
instance.event = event
instance.save()
messages.success(request, 'Item details successfully saved!')
url = reverse('event_admin_dashboard', args=[event_slug, role_id])
return redirect(url)
else:
item_formset = ItemFormSet()
currency_type = payment_details.currency
template = 'registration/item_details.html'
return render(request, template, locals())
I don't believe it is possible to exclude the id field. I'm afraid I can't give you an explanation or a link to the docs.
Aside:
It's not the issue here, but you're missing a comma on your exclude tuple. That's not the problem here, but it means that django interprets it as
exclude = ('i', 'd')
It should be:
exclude = ('id',)