django add-to cart as guest user - django

class Product(models.Model):
title = models.CharField(max_length=120)
slug = models.SlugField(blank=True)
description = models.TextField()
price = models.DecimalField(decimal_places=2, max_digits=20, default=39.99)
image = models.ImageField(upload_to=upload_image_path,null=True, blank=True)
featured = models.BooleanField(default=False)
# quantity = models.IntegerField(default=1, null=True, blank=True)
active = models.BooleanField(default=True)
timestamp = models.DateTimeField(auto_now_add=True)
class OrderItem(models.Model):
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
ordered = models.BooleanField(default=False)
item = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
class Cart(models.Model):
user = models.ForeignKey(User,null=True, blank=True,on_delete=models.CASCADE)
products = models.ManyToManyField(OrderItem, blank=True)
subtotal = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
total = models.DecimalField(default=0.00,max_digits=100,decimal_places=2)
updated = models.DateTimeField(auto_now=True)
timestamp = models.DateTimeField(auto_now_add=True)
"""i am trying to create a ecom website and trying to add products in cart without login
the below code works with login user as request.user how to change it with guest user
"""
def add_cart(request, slug):
item = get_object_or_404(Product,slug=slug)
order_item ,created = OrderItem.objects.get_or_create(
user=request.user,
item =item,
)
print(order_item)
order_qs = Cart.objects.filter(user=request.user)
if order_qs.exists():
order= order_qs[0]
# check if the order item is in the order
if order.products.filter(item__slug=item.slug).exists():
order_item.quantity+= 1
order_item.save()
print("1")
else:
order.products.add(order_item)
else:
order = Cart.objects.create(
user=request.user
)
order.products.add(order_item)
print("done")
return redirect("cart:home")
I am trying to create a ecom website and trying to add products in cart without the below code works with login user with request.user how to change it with guest user and perform add cart and and quandity
to products as guest user

You need to use Django Sessions Framework to achieve this.
Django Sessions Framework
I am not sure that this is the best solution but the below code worked for me like a charm. And use this method if do not want to use JavaScript and only want to stick to the Django Sessions Framework.
In your Cart model:
class Cart(models.Model):
user = models.ForeignKey(User,null=True, blank=True,on_delete=models.CASCADE)
products = models.ManyToManyField(OrderItem, blank=True)
subtotal = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
total = models.DecimalField(default=0.00,max_digits=100,decimal_places=2)
updated = models.DateTimeField(auto_now=True)
timestamp = models.DateTimeField(auto_now_add=True)
ordered = models.BooleanField(default=False)
session_key = models.CharField(max_length=40, null=True)
In your view.py
def add_cart(request, slug):
if request.user.is_authenticated:
item = get_object_or_404(Product,slug=slug)
order_item ,created = OrderItem.objects.get_or_create(
user=request.user,
item =item,
)
print(order_item)
order_qs = Cart.objects.filter(user=request.user, ordered=False)
if order_qs.exists():
order= order_qs[0]
# check if the order item is in the order
if order.products.filter(item__slug=item.slug).exists():
order_item.quantity+= 1
order_item.save()
print("1")
else:
order.products.add(order_item)
else:
order = Cart.objects.create(
user=request.user
)
order.products.add(order_item)
print("done")
return redirect("cart:home")
else:
item = get_object_or_404(Product,slug=slug)
order_item ,created = OrderItem.objects.get_or_create(
user=None,
item =item,
)
print(order_item)
order_qs = Cart.objects.filter(user=None, ordered=False, session_key=request.session.session_key)
if order_qs.exists():
order= order_qs[0]
# check if the order item is in the order
if order.products.filter(item__slug=item.slug).exists():
order_item.quantity+= 1
order_item.save()
print("1")
else:
order.products.add(order_item)
else:
order = Cart.objects.create(
user=None, session_key=request.session.session_key
)
order.products.add(order_item)
print("done")
return redirect("cart:home")
Furter Reference:
Thanks to Todor
Django: Anonymous session & "temporary" one-to-one related model?

You could create a specific user in your database that represents a guest user and assign the cart to that user. However, I do not recommend this.
I assume you want a visitor to be able to save their shopping cart. You can also achieve this by setting a cookie containing the cart data. You'll not need any backend for this as this can be build with javascript.

Related

Filter queryset of django inline formset based on attribute of through model

I have a basic restaurant inventory tracking app that allows the user to create ingredients, menus, and items on the menus. For each item on a given menu, the user can list the required ingredients for that item along with a quantity required per ingredient for that item.
Menu items have a many-to-many relationship with ingredients, and are connected via an "IngredientQuantity" through table.
Here are my models:
class Ingredient(models.Model):
GRAM = 'Grams'
OUNCE = 'Ounces'
PIECE = 'Pieces'
UNIT_CHOICES = [
('Grams', 'Grams'),
('Ounces', 'Ounces'),
('Pieces', 'Pieces')
]
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=200)
unitType = models.CharField(max_length=200, choices=UNIT_CHOICES, verbose_name='Unit')
unitCost = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Unit Cost')
inventoryQuantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Quantity')
def __str__(self):
return self.name + ' (' + self.unitType + ')'
def totalCost(self):
result = self.inventoryQuantity * self.unitCost
return "{:.0f}".format(result)
class Menu(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=200)
timeCreated = models.DateTimeField(auto_now_add=True)
timeUpdated = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.name)
class MenuItem(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
ingredients = models.ManyToManyField(Ingredient, through='IngredientQuantity')
menu = models.ForeignKey(Menu, on_delete=models.CASCADE, null=True)
def __str__(self):
return self.name
def itemCost(self):
relevantIngredients = IngredientQuantity.objects.filter(menuItem=self)
cost = 0
for ingredient in relevantIngredients:
cost += (ingredient.ingredient.unitCost * ingredient.ingredientQuantity)
return cost
class IngredientQuantity(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
menuItem = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
ingredientQuantity = models.IntegerField(default=0)
def __str__(self):
return str(self.ingredient)
This is a multi-user app, so when a user creates a new item on a menu and adds ingredients to it, they should only have the option of choosing ingredients they have created, not those of other users. Here is my attempt to do that in my views:
def ItemUpdate(request, pk):
item = MenuItem.objects.get(id=pk)
user = request.user
IngredientQuantityFormset = inlineformset_factory(
MenuItem, IngredientQuantity, fields=('ingredient', 'ingredientQuantity'), can_delete=True, extra=0
)
form = ItemCreateForm(instance=item)
formset = IngredientQuantityFormset(instance=item, queryset=IngredientQuantity.objects.filter(ingredient__user=user))
if request.method == 'POST':
form = ItemCreateForm(request.POST, instance=item)
formset = IngredientQuantityFormset(request.POST, instance=item, queryset=IngredientQuantity.objects.filter(ingredient__user=user))
# rest of view...
I've searched everywhere for how to implement the queryset parameter properly, but I cannot get it to work. When creating an item on a menu, the user still has the ability to choose from every ingredient in the database (including the ones created by other users). I would like the user to only be able to choose from the ingredients they themselves created.
Does anyone know how to do this properly? Thank you!
I received some guidance on Django forums and arrived at a solution which is documented below:
https://forum.djangoproject.com/t/filter-dropdown-options-in-django-inline-formset-based-on-attribute-of-through-model/13374/3

request cart_obj in template tag filter Django

Is it possible to add a cart_obj request in a Django Template filter
I currently have in my template tag:
#register.filter
def existing_product(product):
return CartItem.objects.filter(item_id=product.id).exists()
and in my template:
{% if product|existing_product %}
{% endif %}
the only problem is that this filter only finds the item_id that is equal to the product.id , I want to be able to add an additional filter that looks for the item_id in the cart_obj.
views.py
cart_obj, new_obj = Cart.objects.new_or_get(request)
basically my template tag would be something like:
return CartItem.objects.filter(item_id=product.id, items_cart_id=cart_obj).exists()
with the addition of filtering the 'items_cart_id', but I'm not sure how to do this and what to put in the template 'if' condition.
class CartManager(models.Manager):
def new_or_get(self, request):
cart_id = request.session.get("cart_id", None)
qs = self.get_queryset().filter(id=cart_id)
if qs.count() == 1:
new_obj = False
cart_obj = qs.first()
if request.user.is_authenticated and cart_obj.user is None:
cart_obj.user = request.user
cart_obj.save()
else:
cart_obj = Cart.objects.new(user=request.user)
new_obj = True
request.session['cart_id'] = cart_obj.id
return cart_obj, new_obj
def new(self, user=None):
user_obj = None
if user is not None:
if user.is_authenticated:
user_obj = user
return self.model.objects.create(user=user_obj)
class CartItem(models.Model):
item = models.ForeignKey(Product, blank=True, on_delete=models.CASCADE, null=True)
items_cart = models.ForeignKey('Cart', blank=True, on_delete=models.CASCADE, null=True)
quantity = models.IntegerField(null=True, blank=True)
updated = models.DateTimeField(auto_now=True)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return 'item:{} cart:{} quantity:{}'.format(self.item, self.items_cart, self.quantity)
class Cart(models.Model):
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
subtotal = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
total = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
updated = models.DateTimeField(auto_now=True)
timestamp = models.DateTimeField(auto_now_add=True)
objects = CartManager()
Any ideas?
Thanks
The code to check if the current item (DetailView) is in the user's cart will look something like this.
on your User model add a method (or Cart if you dont want to mess with built-in User)
class Cart(...):
...
def contains_item(self, item):
# check if there exists a CartItem
# instance that hold a relation with given item
return self.cart_item_set().filter(item=item).exists()
so this will return if the given item is in a given cart so your templatetag should look like this
#register.filter
def contains_item(cart, product):
return cart.contains_item(product)
and call it in the template like
{{ cart_obj|contains_item:product }}

Django model query with Foreign key and get logged in user

Hi I am trying to add "fields" to my django projects that would be calculated based on query..
Basically I have 2 models one is a user which is an extension of Abstract user
class CustomUser(AbstractUser):
pass
and my main model is Project
class Project(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(CustomUser, on_delete=models.PROTECT, editable=False)
name = models.CharField(max_length=20, editable=False)
total = models.DecimalField(max_digits=6, decimal_places=2, editable=False)
created = models.DateTimeField(auto_now_add=True, editable=False, null=False, blank=False)
this_month = datetime.datetime.now().month
allprojectsthismonth = Project.objects.filter(created__month=this_month)
def __str__(self):
return self.name
I create Project objects via a web form using this view:
def homepage(request):
if request.method == "POST":
project = Project()
name = request.POST.get('name')
total = request.POST.get('total')
created = datetime.datetime.now()
user = request.user
project.user = user
project.name = name
project.total = total
project.created = created
project.save()
#return HttpResponse(reverse("homepage.views.homepage"))
return render(request, 'homepage.html')
else:
return render(request, 'homepage.html')
What I need now is to have a queryset that gets me the combination of the total of a given user Project object so that I can make calculations on it, how would I go about doing that?
ideally I would get the logged in user and I could add to my view the sum of all Project.object.total with user = currently logged in.
Thanks
EDIT: Would this work?
class Project(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(CustomUser, on_delete=models.PROTECT, editable=False)
name = models.CharField(max_length=20, editable=False)
total = models.DecimalField(max_digits=6, decimal_places=2, editable=False)
created = models.DateTimeField(auto_now_add=True, editable=False, null=False, blank=False)
this_month = datetime.datetime.now().month
projectsthismonth = self.objects.filter(created__month=this_month)
def monthlyTotal(self,request):
projectsthismonth.objects.filter(
user=request.user
).aggregate(
sum_total=Sum('total')
)['sum_total']
I seem to be missing something however.
You can obtain the queryset of the Projects where the user is the logged in user with:
Project.objects.filter(user=request.user)
If you want to make simple aggregates (like sum, count, etc.), you better use .aggregate(…) [Django-doc] since then the calculations are done at the database level. For example:
from django.db.models import Sum
Project.objects.filter(
user=request.user
).aggregate(
sum_total=Sum('total')
)['sum_total']
will retrieve the sum of the totals of the Projects with user=request.user, this will be None if there are no projects related to the user at all.
from django.utils.timezone import now
class Project(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(CustomUser, on_delete=models.PROTECT, editable=False)
name = models.CharField(max_length=20, editable=False)
total = models.DecimalField(max_digits=6, decimal_places=2, editable=False)
created = models.DateTimeField(auto_now_add=True)
def monthlyTotal(self,user):
this_month = now().month
return Project.objects.filter(
created__month=this_month,
user=user
).aggregate(
sum_total=Sum('total')
)['sum_total']
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

How to connect another object (or user) to Django views (add_to_cart)

I am trying to develop an e-comm site. Where parents buy stuff for kids. Parent add children to his/her profile:
class Kid(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=120)
slug = slug = models.SlugField(unique=True)
last_name = models.CharField(max_length=120)
province = models.CharField(choices=PROVINCE_CHOICES, max_length=2, default='AB')
city = models.CharField(max_length=120)
school = models.CharField(max_length=120)
grade = models.CharField(max_length=120)
teacher = models.CharField(max_length=120)
Also, I have OrderItem and Order classes, taken from a tutorial and it works. I have added kid object to OrderItem, maybe I need to add it to Order too.
class OrderItem(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
kid = models.ForeignKey(Kid, on_delete=models.CASCADE)
ordered = models.BooleanField(default=False)
item = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
def __str__(self):
return f'{self.quantity} of {self.item.title}'
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
items = models.ManyToManyField(OrderItem)
order_date = models.DateTimeField(auto_now_add=True)
event_date = models.DateTimeField()
ordered = models.BooleanField(default=False)
In views, I have a define add_to_cart:
def add_to_cart(request, slug):
item = get_object_or_404(MenuItem, slug=slug)
order_item, created = OrderItem.objects.get_or_create(
item=item,
user=request.user,
ordered=False
)
order_qs = Order.objects.filter(
user=request.user,
ordered=False
)
if order_qs.exists():
order = order_qs[0]
if order.items.filter(item__slug=item.slug).exists():
order_item.quantity +=1
order_item.save()
messages.info(request, "Quantity of " + item.title + " was updated.")
return redirect('/menu/')
else:
order.items.add(order_item)
messages.info(request, item.title +" was added to your cart.")
return redirect('/menu/')
else:
event_date = timezone.now()
order = Order.objects.create(
user=request.user,
event_date=event_date
)
order.items.add(order_item)
messages.info(request, item.title +" was added to your cart.")
return redirect('/menu/')
I think I need to add kid object somewhere here like:
order_item, created = OrderItem.objects.get_or_create(
item=item,
user=request.user,
**kid=???
ordered=False
)
but have no idea how.
It would be good if I have a page of a child with a button Make Order, which opens a list of stuff.
Change the url path of add_to_cart to also accept the slug of a kid:
in urls.py:
urlpatterns = [
...,
path('add_to_cart/<str:kid>/<str:item>', add_to_cart, name='add_to_cart'),
...,
]
in views.py:
def add_to_cart(request, kid_slug, item_slug):
kid = get_object_or_404(Kid, slug=kid_slug)
item = get_object_or_404(MenuItem, slug=item_slug)
order_item, created = OrderItem.objects.get_or_create(
item=item,
user=request.user,
kid=kid,
ordered=False
)
...
In the kid page template, supposing you have a kid object instance variable:
{% url 'add_to_cart' kid.slug item.slug %}

FOREIGN KEY constraint failed on deleting objects

When I am going to delete product from my cart it gives me error FOREIGN KEY constraint failed
class Cart(TimeStamp):
user = models.ForeignKey('authentication.User', on_delete=models.CASCADE, related_name='user_carts', null=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True, blank=True)
sub_total_price = models.DecimalField(max_digits=100, decimal_places=2, blank=True, null=True)
quantity = models.PositiveIntegerField(default=1)
here is model first I thought the problem would be on_delete and I changed it to SET_NULL but it was useless and it did not work, I tried to delete all files from migrations folder it also did not solve my problem.
here is views.py
class CartUpdateDestroyView(generics.RetrieveUpdateDestroyAPIView):
queryset = Cart.objects.all()
serializer_class = CartSerializer
permission_classes = (IsOwnerOrAdmin,)
lookup_field = 'id'
def get_queryset(self):
return Cart.objects.filter(user=self.request.user)
in this view except Destroy all are working properly but I cannot delete the object. Any idea please?
views.py
def cart_update(request):
product_id = request.POST.get('product_id')
if product_id is not None:
try:
product_obj = Product.objects.get(id=product_id)
except Product.DoesNotExist:
print("Show message to user, product is gone?")
return redirect("cart:home")
cart_obj, new_obj = Cart.objects.new_or_get(request)
if product_obj in cart_obj.products.all():
cart_obj.products.remove(product_obj)
added = False
else:
cart_obj.products.add(product_obj) # cart_obj.products.add(product_id)
added = True
request.session['cart_items'] = cart_obj.products.count()
# return redirect(product_obj.get_absolute_url())
if request.is_ajax(): # Asynchronous JavaScript And XML / JSON
print("Ajax request")
json_data = {
"added": added,
"removed": not added,
"cartItemCount": cart_obj.products.count()
}
return JsonResponse(json_data)
return redirect("cart:home")
models.py
class Cart(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
products = models.ManyToManyField(Product, blank=True)
subtotal = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
total = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
updated = models.DateTimeField(auto_now=True)
timestamp = models.DateTimeField(auto_now_add=True)
total1 = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
objects = CartManager()
def __str__(self):
return str(self.id)