Shoping cart M2M model with items count in Django - django

I'm trying to create shopping cart system for one of mine pet project.
I have Profile model that is an extension for built-in User model. My Profile model has shopping_cart field which is M2M relationship to Clothing model from another app, which represents my product. In code, Profile model looks like this:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
...
shopping_cart = models.ManyToManyField(
to=Clothing,
related_name="shopping_cart",
blank=True
)
To add new item to shopping_cart field I use request object that has user object which in turn has profile that I need:
request.user.profile.shopping_cart.add(item)
So, I want to let user add multiple instances of the same item to shoping_cart, and then just count how many and what items do user has in his shopping_cart, probably, with Counter from collections.
Then, if I added let's say, 3 items to shopping_cart, I expecting output like this (with Counter from collections):
Counter({<Clothing: 14VQv52T2409iXGC7958>: 3})
But I got this:
Counter({<Clothing: 14VQv52T2409iXGC7958>: 1})
Now, who can explain why this happens, because I thought M2M means I can add multiple instances of same item, and it will display ALL added items.
UPD 1:
Intermediate model:
class ShoppingCart(models.Model):
item = models.ForeignKey(to=Clothing, on_delete=models.CASCADE)
in_profile = models.ForeignKey(to="Profile", on_delete=models.CASCADE)
count = models.PositiveSmallIntegerField(
default=1,
blank=False,
null=False
)
def __str__(self):
return self.item.title
Part where I proceeding request:
def proceed_request(self, request):
item = Clothing.objects.get(pk=request.POST.get("id"))
related_field = request.user.profile.shopping_cart
if item and item in related_field.all():
related_field.count() += 1 # Throws 'SyntaxError: can't assign to function call' error.
related_field.save()
messages.add_message(request, messages.SUCCESS, f"{item.title} has successfully uppdated!")
return HttpResponse(status=200)
related_field.add(item)
messages.add_message(request, messages.SUCCESS, f"{item.title} has successfully added to {self.verbose_field_name}!")
return HttpResponse(status=200)
UPD 2:
Updated proceed_request function. now all works well.
def proceed_request(self, request):
item = Clothing.objects.get(pk=request.POST.get("id"))
related_model = request.user.profile.shopping_cart
if item and item in related_model.all():
cart_item = ShoppingCart.objects.get(item=item)
cart_item.count += 1
cart_item.save()
messages.add_message(request, messages.SUCCESS, f"{item.title} has successfully uppdated!")
return HttpResponse(status=200)
ShoppingCart.objects.create(item=item, in_profile=request.user.profile)
messages.add_message(request, messages.SUCCESS, f"{item.title} has successfully added to {self.verbose_model_name}!")
return HttpResponse(status=200)

With M2M you can add multiple different items of Clothing, but adding the same item multiple times will not duplicate the relation. Have a look at adding a count field to the relation by using through:
https://docs.djangoproject.com/en/3.0/topics/db/models/#extra-fields-on-many-to-many-relationships

Related

how to create a SimpleListFilter in django

I don't succeed to write a query filter.
I have 3 models: Patient, Prescription and User
I write you only what is relevant for my question
Patient:
class Patient(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
Prescription:
class Prescription(models.Model):
user = models.ForeignKey(
User,
null=True,
blank=False,
on_delete=models.DO_NOTHING
)
file_extention = models.CharField(
'file extention',
max_length=8,
null=True,
blank=True,
)
So the relation between both of models (Patient and Prescription) are through User.
in the PatientAdmin, I want to filter on the file_extension according pdf or jpg of the prescription uploaded.
I created a SimpleListFilter but impossible to find the right query.
class PrescriptionFileExtensionFilter(SimpleListFilter):
"""This filter is being used in django admin panel in
patient model."""
title = 'Prescription File Ext'
parameter_name = 'file_extention'
def lookups(self, request, model_admin):
return (
('pdf', 'PDF'),
('jpg', 'JPG'),
)
def queryset(self, request, queryset):
for user in queryset:
if self.value() == 'pdf':
return queryset.filter(user=user.user).filter
(prescription__file_extention="pdf")
if self.value() == 'jpg':
return queryset.filter(user=user.user).filter
(prescription__file_extention="jpg")
That's not working...
Do I need the for user in queryset:
need What could be the query to bring me all the users with a prescription with file_extension = "pdf" (or "jpg")
You are trying to get a key from the prescription object in print(mydict['file_extention']) which I believe is causing the issue - you would instead access that property via mydict.file_extention - though I should add that mydict is not an accurate variable name for a model object as it isn't a dictionary. I don't think that for loop is actually doing anything other than printing a particular value so it can be removed altogether.
As an aside, you have two filters on your queryset, this can just be expressed as a single filter, separate by a comma, e.g.
return queryset.filter(user=user.user, prescription__file_extention="pdf")
You are also calling user.user, presumably you just want to get the user model which is kept in request.user - is that what your for loop was trying to do?
Edit
If you want to get all of the users but just filtered by JPG or PDF then you need to remove two things:
The for-loop of for user in queryset
The filter of .filter(user=user.user)
The for loop is unnecessary in the queryset function and the filter is just getting a single user, but you want to get all of them - correct?

How can i Edit/Change the value of a model field from views.py

Good-day everyone. I want to know how i can change the value of a model-field through the number of items in a defined sessions list
I have already made a profile model (which is OneToOne Field to the user model)
with a 'level' field as shown below in my models.py;
And in view.py, I have created a session called 'answer_list' which is a list that stores all correct answers provided by the user.
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
level = models.CharField(max_length=1, choices=[('Newbie', 'N'), ('Regular', 'R'), ('Expert', 'E')], default='Newbie')
views.py
def index(request):
if 'answer_list' in request.session: #answer_list has been created previously #request.session['answer_list'] = []
ok = request.session['answer_list']
print (ok) #just to check the content of 'answer_list' which is correct
if len(ok) == 4:
user=request.user
user.profile.level = 'R'
user.profile.save()
return render(request, 'index.html', {})
I want the value of the user.profile.level to change from 'Newbie' to 'Regular' once the number of items in the session 'answer_list' gets to 4. Please how can i go about it
Try this:
UserProfile.objects.filter(user=request.user).update(level='R')

How to get object using filter on ManyToManyField

Why target_dialogue is always None?
Model:
class Dialogue(models.Model):
name = models.CharField(max_length=30, blank=True)
is_conference = models.BooleanField(default=False)
participants = models.ManyToManyField(
Person,
related_name='dialogues',
)
def __str__(self):
return self.name or str(self.pk)
And in view I want to get suitable dialogue which contain in participants field 2 objects - user and companion. And if this dialogue doesn't exist I create it:
target_dialogue = None
try:
target_dialogue = Dialogue.objects.get(is_conference=False,participants__in=[user, companion])
except ObjectDoesNotExist:
target_dialogue = Dialogue()
target_dialogue.save()
target_dialogue.participants.add(user)
target_dialogue.participants.add(companion)
finally:
return render(request, 'dialogues/dialogue.html', {
'dialogue': target_dialogue,
})
But target_dialogue is always None. What's a reason of it? I was supposed to solve only a trouble in getting a dialogue from db in order to bad filter parameters, but now I have doubts about it. Maybe something else?
request.user is not a object of Person model with which you have the relation in Dialogue.
You have to first fetch the person object:
user = Person.objecs.get(user=request.user). # According to your person model
Follow same for companion and then query:
target_dialogues = Dialogue.objects.filter(is_conference=False,participants__in=[user,companion]

Django 1054 Unknown Field error

I'm attempting to add a new foreign key to an existing model of mine, but I'm having issues getting it to save the values from the UI. When I get to the relevant page, the data populates correctly, but when I attempt to save a change of the foreign key I get a 1054 "Unknown column 'schedule_id' in 'field list'" error. Strangely, if I refresh the page, the changes were saved regardless of the error.
Relevant models:
class Group(BaseModel):
code = CharField()
name = CharField()
calendar = models.ForeignKey(Calendar)
schedule = models.ForeignKey(Schedule, null=True, blank=True, default=None) # this is the new FK
class Schedule(BaseModel):
code = CharField()
name = CharField()
description = CharField()
#various integer fields
I'm using South for my database migrations:
def forwards(self, orm):
db.add_column('group', 'schedule',
self.gf('django.db.models.fields.related.ForeignKey')\
(to=orm['app.Schedule'], blank=True, null=True)\
)
The view is pretty simple:
def api_group(jsonDict, request):
if request.method == "POST":
#other handlers
elif operation == "edit":
_update_group(request)
def _update_group(request):
group = Group.objects.get(id=request.POST.get('id'))
formData = request.POST.copy()
form = GroupForm(formData, instance=group)
if form.is_valid():
form.save()
class GroupForm(renderutils.BaseModelForm):
id = forms.CharField(widget=forms.HiddenInput())
class Meta(renderutils.BaseModelForm.Meta):
model = Group
When I look at the 'group' table, I can see the field named 'schedule_id' and correct values are getting assigned on save, I just don't understand why the error is being raised if nothing is going wrong? I've tried doing a backward and forward south migration multiple times to make sure it wasn't something wrong with it.

django-rest-framework - trying to set required=False flag on nested 1-to-M?

I'm having some issue with django-rest-framework, and nested objects.
I have a Cart object, as well as CartItem, which links back to a Cart:
class Cart(models.Model):
customer = models.ForeignKey(Customer)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='cartitems')
product = models.ForeignKey(Product, help_text='Product in a cart')
quantity = models.PositiveIntegerField(default=1, help_text='Quantity of this product.')
date_added = models.DateTimeField(auto_now_add=True, help_text='Date that this product was added to the cart.')
I've created serializers for both:
class CartItemSerializer(serializers.ModelSerializer):
product = serializers.HyperlinkedRelatedField(view_name='product-detail')
class Meta:
model = CartItem
class CartSerializer(serializers.ModelSerializer):
customer = serializers.HyperlinkedRelatedField(view_name='customer-detail')
cartitems = CartItemSerializer(required=False)
total_price = serializers.CharField(source='total_price', read_only=True)
shipping_cost = serializers.CharField(source='shipping_cost', read_only=True)
class Meta:
model = Cart
fields = ('id', 'customer', 'date_created', 'date_modified', 'cartitems', 'total_price', 'shipping_cost')
However, whenever I try to POST to create a new cart, I get an error, assumedly when it tries to set the non-existent CartItem:
TypeError at /api/v1/carts/
add() argument after * must be a sequence, not NoneType
However, a Cart isn't required to actually have CartItems.
Is there any way to get DRF to respect the required=False flag I get on Cart.cartitems?
Cheers,
Victor
EDIT:
I took a stab at tracing it through again:
It's calling BaseSerializer.save() in rest_framework/serializers.py with a CartSerializer object.
def save(self, **kwargs):
"""
Save the deserialized object and return it.
"""
if isinstance(self.object, list):
[self.save_object(item, **kwargs) for item in self.object]
if self.object._deleted:
[self.delete_object(item) for item in self.object._deleted]
else:
self.save_object(self.object, **kwargs)
return self.object
It then calls save_object() on the same class:
def save_object(self, obj, **kwargs):
"""
Save the deserialized object and return it.
"""
if getattr(obj, '_nested_forward_relations', None):
# Nested relationships need to be saved before we can save the
# parent instance.
for field_name, sub_object in obj._nested_forward_relations.items():
if sub_object:
self.save_object(sub_object)
setattr(obj, field_name, sub_object)
obj.save(**kwargs)
if getattr(obj, '_m2m_data', None):
for accessor_name, object_list in obj._m2m_data.items():
setattr(obj, accessor_name, object_list)
del(obj._m2m_data)
if getattr(obj, '_related_data', None):
for accessor_name, related in obj._related_data.items():
if isinstance(related, RelationsList):
# Nested reverse fk relationship
for related_item in related:
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
setattr(related_item, fk_field, obj)
self.save_object(related_item)
# Delete any removed objects
if related._deleted:
[self.delete_object(item) for item in related._deleted]
elif isinstance(related, models.Model):
# Nested reverse one-one relationship
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
setattr(related, fk_field, obj)
self.save_object(related)
else:
# Reverse FK or reverse one-one
setattr(obj, accessor_name, related)
del(obj._related_data)
The Cart object has a _related_data field that is set to a dict:
{'cartitems': None}
Hence, on the second-last line, it calls setattr in django/db/models/fields/related.py:
def __set__(self, instance, value):
if instance is None:
raise AttributeError("Manager must be accessed via instance")
manager = self.__get__(instance)
# If the foreign key can support nulls, then completely clear the related set.
# Otherwise, just move the named objects into the set.
if self.related.field.null:
manager.clear()
manager.add(*value)
It's this last liner (manager.add(*value)) that causes the:
TypeError: add() argument after * must be a sequence, not NoneType
Checking the Serializer Relation Docs, first you need to add many=True to your cartitems field.
Unfortunately this is read-only. The docs just say "For read-write relationships, you should use a flat relational style" — you can find a question about that here (although that's only dealing with the 1-1 case).
Current strategies involve making cartitems read-only and then either: doing something post_save, using a second serializer or making a separate request to a separate endpoint to set the related entities. Given that better support for Nested Writes is coming I'd probably be inclined towards a separate request to a separate endpoint for the moment (though that will obviously depend on your constraints).
I hope that helps.
EDIT: (After update to question & discussion in comments).
If you're using a separate endpoint for adding CartItems then making cartitems read-only should eliminate the error.
However (if you're not making it read-only) looking at the DRF code you posted from save_object it occurs that in the related_item in related block you really do need a list. The appropriate dict (fragment) for a Cart with no CartItems is not {'cartitems': None} but rather {'cartitems': []}. — This of course means your required=False flag isn't doing anything. (So perhaps the short answer is "No" — Will now defer to the mailing list discussion