When I do this exactly as provided below, a shipping address object is created without the customer assigned in the shipping address foreignkey field, I can add it from the admin panel manually but I'm not able to make it work through code, idk what I'm doing wrong, please help!
**models.py**
class Customer(models.Model):
user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, blank=True, null=True)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(max_length=150)
class ShippingAddress(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, blank=True, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, blank=True, null=True)
address_one = models.CharField(max_length=200)
address_two = models.CharField(max_length=200)
...
**views.py**
def checkout(request):
if request.user.is_authenticated:
customer = request.user.customer
order, created = Order.objects.get_or_create(customer=customer, complete=False)
items = order.orderitem_set.all()
else:
items = []
order = {'get_cart_total': 0, 'get_cart_items': 0}
if request.method == 'POST':
form = ShippingForm(request.POST)
if form.is_valid():
#how do I get the customer to get added in the foreignkey field for the shipping address model
form.save()
return redirect('store:checkout_shipping')
else:
form = ShippingForm()
else:
form = ShippingForm()
context = {"items": items, "order": order, "form": form}
return render(request, 'store/checkout.html', context)
In response to your comment #how do I get the customer to get added... etc, in the case that your ShippingForm() points to your ShippingAddress model, or at least something with a customer foreign key field, you may need to do something like this:
def checkout(request):
if request.user.is_authenticated:
customer = request.user.customer
order, created = Order.objects.get_or_create(customer=customer, complete=False)
items = order.orderitem_set.all()
else:
items = []
order = {'get_cart_total': 0, 'get_cart_items': 0}
if request.method == 'POST':
form = ShippingForm(request.POST)
if form.is_valid():
new_shipment = form.save(commit=False)
new_shipment.customer = customer
new_shipment.save()
return redirect('store:checkout_shipping')
else:
form = ShippingForm()
else:
form = ShippingForm()
context = {"items": items, "order": order, "form": form}
return render(request, 'store/checkout.html', context)
Using commit=False on the form.save() will allow you to subsequently modify other fields, in this case, by adding the customer relation, then saving it. More information here in the Django documentation. Salient quote:
This save() method accepts an optional commit keyword argument, which
accepts either True or False. If you call save() with commit=False,
then it will return an object that hasn’t yet been saved to the
database. In this case, it’s up to you to call save() on the resulting
model instance. This is useful if you want to do custom processing on
the object before saving it, or if you want to use one of the
specialized model saving options. commit is True by default.
"Custom processing" in this case is the creation of the foreign key relationship to the model instance (customer).
Related
I currently have models shown below
class Product(models.Model):
name = models.CharField(max_length=200, null=True)
stock = models.CharField(max_length=200, null=True)
class Order(models.Model):
product = models.ForeignKey(Product, null=True, on_delete = models.SET_NULL)
As you can see the order model has the product model as a foreign key.
When I create or update instances based upon on the order model, I would like to decrement one value from the stock field in the products model.
See below for the my views for both when creating and updating instances.
#login_required
def newOrder(request):
form = CreateOrderForm()
if request.method == 'POST':
form = CreateOrderForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('customer_order_list')
return render(request, 'accounts/new_order.html', {'form': form})
#login_required
def editOrder(request, pk):
order = Order.objects.get(id=pk)
sho = Order.objects.all().values_list('date_created')
order_date = sho.filter(id=pk)
form = CreateOrderForm(instance=order)
if request.method == "POST":
form = CreateOrderForm(request.POST, instance=order)
if form.is_valid():
form.save()
return redirect('customer_order_list')
return render(request, 'accounts/edit_order.html', {'form': form, 'order_date': order_date})
I am aware that using the a similar example below will need to be implemented, however I will have to use the primary key of that particular instance.
with transaction.atomic():
product = (
Product.objects
.select_for_update()
.get(id=1)
)
product.inventory -= 1
product.save()
However this example will not apply for when creating instance based on the order form.
How can one implement this?
If I'm not mistaken, you're trying to update Product.stock when an Order is made. You can use signals for this. For example:
from django.db.models.signals import post_save
#receiver(post_save, sender=Order)
def update_stock(sender, instance, created, **kwargs):
if created:
instance.product.stock = instance.product.stock - 1
instance.product.save()
Say, I have a view function that creates a Vacancy object on saving the form, and it has the field company that, if the form is valid, has to be assigned with value of field name, that is on the other model named EmployerProfile, how can I do that? I have company as a foreign key on my model.
My models
class EmployerProfile(models.Model):
name = models.CharField(max_length = 64)
description = models.TextField()
username = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete = models.CASCADE)
class Vacancy(models.Model):
name = models.CharField(max_length = 64)
competences = models.CharField(max_length = 32)
salary = models.DecimalField(decimal_places = 2, max_digits = 10)
description = models.TextField(null = True)
company = models.ForeignKey(EmployerProfile, on_delete = models.CASCADE)
featured = models.BooleanField(default = False)
My view
#login_required(login_url = 'login')
def vacancyAddView(request):
if request.method == 'POST':
form = VacancyAddForm(request.POST)
if form.is_valid():
form.save()
return redirect('profile')
else:
form = VacancyAddForm()
context = {
'form':form
}
return render(request, "addVacancy.html", context)
My form
class VacancyAddForm(forms.ModelForm):
class Meta:
model = Vacancy
fields = [
'name',
'competences',
'description',
'salary',
]
P.S. I have tried adding this piece of code to my view, rigth after form.is_valid():
obj = EmployerProfile.objects.get(username = request.user)
form.instance.company = obj.name
but it didn't work, it returned the ValueError with text "Vacancy.company" must be a "EmployerProfile" instance.
P.P.S. I also tried to override the save method in my model like this
def save(self, *args, **kwargs):
self.company = self.EmployerProfile.name
super(Vacancy, self).save(*args, **kwargs)
but it didn't work either.
You can't do what you want. Your Vacancy model defines company as a foreign key, but you're trying to set it as a string.
class Vacancy(models.Model):
company = models.ForeignKey(EmployerProfile, on_delete = models.CASCADE)
However, here's what your view should change to be to work with your models as you've defined them currently.
#login_required(login_url = 'login')
def vacancyAddView(request):
if request.method == 'POST':
form = VacancyAddForm(request.POST)
if form.is_valid():
# Set the company
form.instance.company = request.user.employerprofile
form.save()
return redirect('profile')
else:
form = VacancyAddForm()
context = {
'form':form
}
return render(request, "addVacancy.html", context)
The basic issue here is that you can't set a ForeignKey field to be a string. If you want the user to be able to set the Company from the form, you have a couple of choices. One is to take what you have now in the form and use it to filter, e.g. something like:
company = EmployerProfile.objects.filter(name__iexact=form.cleaned_data['company'])
form.instance.company = company
Another would be to change the form so that instead of a text input, company is a ModelChoiceField, or a regular ChoiceField populated with the company choices available to the user.
In either case the end goal will be to ensure that the company field contains a foreign key to an EmployerProfile object, rather than the string name of a company.
I have a problem with an order view of my Django application.
The whole error:
Value Error: Cannot assign "SimpleLazyObject: User: wad2": "OrderItem.order"
must be a "Order" instance.
This is the order view:
#login_required
def my_order(request):
user = request.user
context = {}
cart = Cart(request)
if request.method == 'POST':
form = OrderCreateForm(request.POST or None, instance=user)
if form.is_valid():
order = form.save()
for item in cart:
OrderItem.objects.create(
order=order,
product=item['product'],
price=item['price'],
quantity=item['quantity'])
cart.clear()
context['order'] = order
else:
form = OrderCreateForm()
context['form']=form
return render(request, 'iFood/my-order.html', context)
The form:
class OrderCreateForm(forms.ModelForm):
class Meta:
model = Order
fields = ('created’,)
The models for OrderItem and Order:
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT, related_name='orders')
created = models.DateField(default=datetime.date.today())
def __str__(self):
return 'Order {}'.format(self.id)
def get_total_cost(self):
return sum(item.get_cost() for item in self.items.all())
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
product = models.ForeignKey(Product, related_name='order_items', on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.PositiveIntegerField(default=1)
def __str__(self):
return '{}'.format(self.id)
def get_cost(self):
return self.price * self.quantity
The reason I'm using the form is just to confirm the user wants to create the order. I know there's something wrong with the way I create the OrderItem and reference order there but I don't know how to resolve it.
You use instance in a model form when you want to edit an existing instance. If the model form is for Order, then the instance must be an order. You are passing a User instance, so you get the error.
In your case, you are creating a new order so you don't have to pass an instance. You can save the form with commit=False, and then set the user:
if request.method == 'POST':
form = OrderCreateForm(request.POST or None)
if form.is_valid():
order = form.save(commit=False)
order.user = request.user
order.save()
...
Since you don't get the user to fill in any fields in the form, you could get rid of the form completely. You can still check request.method, and that way you will only create the order for POST requests.
if request.method == 'POST':
order = Order.objects.create(user=request.user)
...
I have a many-to-many field and in order to save the retrieved data from the form, initially, I have to save an instance of that table, otherwise, I get an error like:
"Order: None" needs to have a value for field "id" before this many-to-many relationship can be used.
But the issue is when I have 2 save methods called, they also create 2 different logs(at the first save() it creates the instance and at the letter one it edits it) and this cause the chaos. Therefore, I want to be able to commit=False in the first one and finalize the save in the second save(). Commit is used for ModalForm, however, my form extends form.Forms.
views.py
if request.method == 'POST':
form = OrderAddForm(request.POST)
if form.is_valid():
order = Order()
order.user = request.user
order.save()
order.ordered_materials.set(form.cleaned_data['ordered_materials'])
order.location = form.cleaned_data['location']
order.note = form.cleaned_data['note']
form.save()
else:
form = OrderAddForm()
forms.py
class OrderAddForm(forms.Form):
ordered_materials = forms.ModelMultipleChoiceField(
queryset=Material.objects.all(),
)
location = forms.CharField()
note = forms.CharField()
ordered_materials.widget.attrs.update({'id': 'materialsid',
'class': 'form-control',
})
models.py
class Order(models.Model):
user = models.ForeignKey(User,
on_delete=models.CASCADE)
ordered_materials = models.ManyToManyField('Material')
location = models.CharField(max_length=500)
note = models.CharField(max_length=30,
verbose_name="Note")
STATUS = (
('n', 'None'),
('a', 'Accepted'),
('r', 'Rejected'),
)
status_name = models.CharField(max_length=1,
choices=STATUS,
default='n')
def __str__(self):
return str(self.id)
You don't have to save the object after setting the many-to-many field. Therefore you should be able to re-arrange your code so that you only call save() once.
if form.is_valid():
order = Order()
order.user = request.user
order.location = form.cleaned_data['location']
order.note = form.cleaned_data['note']
order.save()
order.ordered_materials.set(form.cleaned_data['ordered_materials'])
I am busy trying to get the id only in integer format preferably for the ModelChoiceField. I get the list to display but get's returned in a string format. Please helping me in retrieving the id of ModelChoiceField. I think I need to do this in the view.
forms.py
class ProjectForm(forms.ModelForm):
items = forms.ModelChoiceField(queryset=Project.objects.all())
class Meta:
model = Project
fields = ['items']
models.py
class Project(models.Model):
items = models.IntegerField(default=0, blank=True, null=True)
views.py
def ProjectView(request):
form = ProjectForm(request.POST)
if request.method == 'POST':
if form.is_valid():
save_it = form.save(commit=False)
save_it.save()
return HttpResponseRedirect('/')
else:
form = ProjectForm()
return render(request, 't.html', {'form': form })
From what I can tell, items should never be an IntegerField. Your usage has it set up to be a ForeignKey to a Project so you should just make that explicit
items = models.ForeignKey('self', null=True, blank=True)
Possibly with a better descriptive name than items.
Then, you don't need to define anything on the form, it just becomes a standard model form, with a standard model form usage.