Make change to selected object in many to many fields - django

I have a Order model and item model with many to many relationship, but I just want to make change to selected items for each Order, How can I do that? As when I get order.items.all will affect all the items in the Order.
Model.py:
class OrderItem(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
item = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
ordered = models.BooleanField(default=False)
being_delivered = models.BooleanField(default=False)
being_delivered_date = models.DateTimeField(null=True)
Received = models.BooleanField(default=False)
Received_date = models.DateTimeField(null=True)
def __str__(self):
return f"{self.quantity} of {self.item.product_name}"
class Order(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
items = models.ManyToManyField(OrderItem)
ordered = models.BooleanField(default=False)
being_delivered = models.BooleanField(default=False)
being_delivered_date = models.DateTimeField(null=True)
Received = models.BooleanField(default=False)
Received_date = models.DateTimeField(null=True)
def __str__(self):
return self.user.username
Views.py
def update_to_recevied(request, id):
order_item = get_object_or_404(OrderItem, id=id)
order_item.Received = True
order_item.Received_date = timezone.now()
order_item.save()
return redirect(...)
html:
{% for item in order.items.all %}
Received<br><span>{{ item.item.product_name }}</span>
{% endfor %}

you can get the selected item by using post form then send it to the views.py then resend it to the template as dictionary

I am not sure if I understand your goal correctly, But if you want to update only selected items you can use Jquery. Use a checkbox instead of a button,give an id to your checkbox
<input type="checkbox" id="checkbox">Received</input>
you can check whether that item is checked or not like this :
<script>
$("#checkbox").change(function() {
if(this.checked) {
$.post("{% url 'update_to_recevied' item.id %}", {
received: "True",
},
});
}
});
</script>
or simply use :checked selector
https://api.jquery.com/checked-selector/
and you can take look at Jquery Post requests here
https://www.w3schools.com/jquery/jquery_ajax_get_post.asp

Related

How to render a foreign key field in Django

Desired outcome: When I render a Poller and its associated comments
I would like to also render the Vote a user selected for the Poller along with his comment (Note: A user can only comment if he voted on that poller).
Side note: A user can make one vote to a Poller and post one comment to a Poller. He can only comment if he voted beforehand.
# Models
class Poller(models.Model):
poller_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_on = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(Account, on_delete=models.CASCADE)
poller_text = models.CharField(max_length=333)
poller_choice_one = models.CharField(max_length=20)
poller_choice_two = models.CharField(max_length=20)
class Vote(models.Model):
poller = models.ForeignKey(Poller, on_delete=models.CASCADE, related_name='vote')
user = models.ForeignKey(Account, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
poller_choice_one_vote = models.BooleanField(default=False)
poller_choice_two_vote = models.BooleanField(default=False)
def __str__(self):
return f'Vote by {self.user}'
class Comment(models.Model):
poller = models.ForeignKey(Poller, on_delete=models.CASCADE, related_name='PollerComment')
user = models.ForeignKey(Account, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
comment = models.TextField(max_length=350)
flag_count = models.IntegerField(default=0)
upvote_count = models.IntegerField(default=0)
downvote_count = models.IntegerField(default=0)
# View
#require_GET
def render_single_poller(request, poller_id):
# Retrieve comments associated to the Poller
comments_qs = PollerComment.objects.filter(poller_id=poller_id)
context = {
'comments_qs': comments_qs,
}
return render(request, 'pollboard/single_poller.html', context)
I tried to do it via a template filter like so:
# pollboard_tags.py
#register.filter(name='get_vote')
def get_voted(self):
self.vote_made = 'Test'
print(self.vote.poller_choice_one_vote)
if self.vote.poller_choice_one_vote:
self.vote_made = 'One'
else:
self.vote_made = 'Two'
return self.vote_made
# template
<div class="commentors-poller-choice">{{ comment|get_vote }}</div>
throws
RelatedObjectDoesNotExist at /poller/68c725eb-277e-4b5b-a61b-b4a02bf5e854/
PollerComment has no vote.
I fear that I'm already overcomplicating things here. I hope there is a more straightforward solution to this like idk expanding the comments queryset by the required information for rendering?
If a user can vote on a poller only once, you can filter with:
#register.filter(name='get_vote')
def get_voted(self):
vote = Vote.objects.get(poller=self.poller, user=self.user)
return 'One' if vote.poller_choice_one_vote else 'Two'

Django: Problem with filtering products in order

I'm trying to make account view in my django-shop. I want to display information about the order and the ordered goods. I have a ProductInOrder model with foreign key to Order. Now I want to filter the ordered goods by order. But something is going wrong.
class Order(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
ref_code = models.CharField(max_length=15)
items = models.ForeignKey(Cart, null=True ,on_delete=models.CASCADE, verbose_name='Cart')
total = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True)
phone = models.CharField(max_length=20)
buying_type = models.CharField(max_length=40, choices=BUYING_TYPE_CHOICES,
default='Доставка')
address = models.CharField(max_length=250, blank=True)
date_created = models.DateTimeField(default=timezone.now)
date_delivery = models.DateTimeField(default=one_day_hence)
comments = models.TextField(blank=True)
status = models.CharField(max_length=100, choices=ORDER_STATUS_CHOICES,
default='Принят в обработку')
class ProductInOrder(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
item_cost = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
all_items_cost = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
And views.py
def account_view(request):
order = Order.objects.filter(user=request.user).order_by('-id')
products_in_order = ProductInOrder.objects.filter(order__in=order)
categories = Category.objects.all()
instance = get_object_or_404(Profile, user=request.user)
if request.method == 'POST':
image_profile = ProfileImage(request.POST, request.FILES, instance=instance)
if image_profile.is_valid():
avatar = image_profile.save(commit=False)
avatar.user = request.user
avatar.save()
messages.success(request,
f'Ваш аватар был успешно обновлен!')
return redirect('ecomapp:account')
else:
image_profile = ProfileImage()
context = {
'image_profile': image_profile,
'order': order,
'products_in_order': products_in_order,
'categories': categories,
'instance': instance,
}
return render(request, 'ecomapp/account.html', context)
This line products_in_order = ProductInOrder.objects.filter(order__in=order) doesn't work.
Any help please.
Unless you explicityly mention order_by in ProductInOrder queryset, it will order by its default setup, which is mentioned in ProductInOrder model's meta class(if its not mentioned, then default ordering is pk). So using following line should resolve your issue:
ProductInOrder.objects.filter(order__in=order).order_by('-order')
But an improved answer is like this:
products_in_order = ProductInOrder.objects.filter(order__user=request.user).order_by('-order')
In this way, you can remove line order = Order.objects.filter(user=request.user).order_by('-id') from your code. Whats happening here is that, django allows nested filtering, so you can filter by order__user which will allow you order by user from Order model. You don't need to make a filter for Order separately.
Update:
I am not sure, probably you are looking for this:(in template)
{% for o in order %}
{% for po in o.productinorder_set.all %}
{{ po.product }}
{{ po.item_cost }}
{% endfor %}
{% endfor %}
Here I am using reverse relation between Order and ProductInOrder here.

Retrieve a user list from a manydomanyfield with information from an intermediate table

it's been a few hours since I tried to retrieve a list of users with the information of an intermediate table.
So I have a workspace model that is a manytomanyfield with users
There is also an intermediary table to differentiate the classic users and the workspace manager
I would like to display the list of users and add a small icon symbolizing the managers in the list.
But unfortunately it seems difficult for Django, to display both the list of users of the workspace with the information of the intermediate table.
In any case I look at the documentation of Django I have not managed to find how to do.
models.py
class Workspace(models.Model):
name = models.CharField(max_length=250, verbose_name="Nom du workspace")
members = models.ManyToManyField(User, through='Membership', verbose_name="Membres du workspace")
token = models.CharField(max_length=500) # token statique
join_token = models.CharField(max_length=500) # token dynamique
join_token_date = models.DateTimeField(auto_now_add=False, null=True, blank=True)
payday = models.DateField(max_length=10, verbose_name="Jour de paye", null=True, blank=True)
planning_image = ProcessedImageField(upload_to='planning',
null=True,
blank=True,
processors=[ResizeToFill(1299, 937)],
format='JPEG',
options={'quality': 100})
planning_thumbnail = ImageSpecField(source='planning_image',
processors=[ResizeToFill(280, 202)],
format='JPEG',
options={'quality': 100})
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('create-workspace')
class Membership(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE)
is_manager = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
views.py
#login_required
def workspace_detail(request, token):
ins_workspace = get_object_or_404(Workspace, token=token)
list_members = ins_workspace.members.all()
for member in list_members:
if member == request.user:
current_user = Membership.objects.get(workspace=ins_workspace, user=request.user)
context = {
'name': ins_workspace.name,
'token': ins_workspace.token,
'list_members': list_members,
'payday': ins_workspace.payday,
'is_manager': current_user.is_manager,
}
return render(request, 'workspace/workspace_detail.html', context)
else:
return HttpResponseForbidden()
template.html
{% for item in list_members %}
{{ item.username }}
{% endfor %}
This is what I want:
template.html
{% for item in list_members %}
{% item.is_manager %}
{{ item.username }} (♔)
{% else %}
{{ item.username }}
{% endfor %}
You can do it like this:
Update Membership model with related name:
class Membership(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="membership")
workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE)
is_manager = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
Then you can update your view like following:
from django.db.models import F
#login_required
def workspace_detail(request, token):
ins_workspace = get_object_or_404(Workspace, token=token)
list_members = ins_workspace.members.all().annotate(is_manager=F('membership__is_manager'))
context = {
'name': ins_workspace.name,
'token': ins_workspace.token,
'list_members': list_members,
'payday': ins_workspace.payday,
'is_manager': request.user.membership.get(workspace=ins_workspace).is_manager,
}
return render(request, 'workspace/workspace_detail.html', context)
That should do the trick.
Here what I have done is that, I am using a reverse relation to get is_manager value from membership model. I am annotating that value in the queryset using F.

Making a CreateView in Django using kwargs from a different model template

I am not able to create the object review using the CreateView I am not sure what I am doing wrong. Below is a brief intro
Intro: My Order history page is where a user can see all the items that he/she has bought. In the Order history page I have a button that lets the buyer leave a review for the seller. Below is the button
<a href="{% url 'accounts:review' username=item.made_by pk=item.pk %}">
<button class="text-success">Leave Review</button>
</a>
from here I get the items item.id and the sellers username
{% for item in order.items_in_this_order.all %} <!--models below -->
url(r'^(?P<username>[-\w]+)/(?P<pk>\d+)/review/$', views.ReviewCreate.as_view(), name='review'),
{% endfor %}
The url expresses correctly in the address bar when I click on leave a review. It then displays a form. where I enter feedback, ratings, feedback_image, feedback_video the rest of the fields are supposed to be made in the views.py. After I fill the form and hit submit. The url is still correct. But I get the below error
IntegrityError at /accounts/nikhil/10/review/
NOT NULL constraint failed: accounts_review.item_id
Request Method: POST
Request URL: http://127.0.0.1:8000/accounts/nikhil/10/review/
Django Version: 1.11
Exception Type: IntegrityError
Exception Value:
NOT NULL constraint failed: accounts_review.item_id
Below are the views.py this is inside the accounts app
class ReviewCreate(LoginRequiredMixin, CreateView):
model = Review
form_class = ReviewCreateForm
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.review_from = self.request.user
print(self.object.review_from) #This prints
self.item = OrderItem.objects.get(pk=self.kwargs.get('pk'))
print(self.item) #This prints
self.object.review_for = User.objects.get(username=self.kwargs.get('username'))
print(self.object.review_for) #This prints
self.object.save()
return super().form_valid(form)
Below are the models.py for Review models
class Review (models.Model):
review_from = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='review_from')
review_for = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='review_for')
item = models.ForeignKey(OrderItem, related_name='items')
created = models.DateTimeField(auto_now_add=True)
feedback = models.TextField(blank=True, null=True)
feedback_image = models.ImageField(blank=True, null=True)
feedback_video = models.FileField(blank=True, null=True)
rating_choices = (
('1', 'One'), ('2', 'Two'), ('3', 'Three'), ('4', 'Four'), ('5', 'Five'),
('6', 'Six'), ('7', 'Seven'), ('8', 'Eight'), ('9', 'Nine'), ('10', 'Ten')
)
ratings = models.CharField(max_length=2, choices=rating_choices)
def __str__(self):
return 'Review from {} to {} for {}'.format(self.review_from, self.review_for, self.item.product)
Below are the models.py for OrderItem just in case
class OrderItem(models.Model):
product = models.CharField(max_length=350)
quantity = models.IntegerField()
price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='USD Price')
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items_in_this_order')
date = models.DateField()
time_from = models.TimeField()
time_to = models.TimeField()
made_by = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, related_name='product_by')
image = models.ImageField()
order_date = models.DateField(auto_now_add=True)
picked = models.ManyToManyField(User, blank=True, related_name='item_picked')
Your form_valid method is quite strange. Among other things, you set the item to self.item, which isn't used anywhere and certainly isn't set on the created instance. Additionally, you create and save the object there directly, but then call the superclass method which will do it again without your additions.
Instead you should set all these attributes on form.instance, then let the super method do the saving:
def form_valid(self, form):
form.instance.review_from = self.request.user
form.instance.item = OrderItem.objects.get(pk=self.kwargs.get('pk'))
form.instance.review_for = User.objects.get(username=self.kwargs.get('username'))
return super().form_valid(form)

Save a list of objects in model as a field

I'm trying to write internet-shop, and I have a model Order:
class Order(models.Model):
state_choices = ('ACTIVE', 'COMPLETED', 'FROZEN')
order_date = models.DateTimeField(auto_now_add=True)
delivery_time = models.CharField(max_length=100)
address_city = models.CharField(max_length=40)
address_street = models.CharField(max_length=40)
address_building = models.CharField(max_length=40)
state = models.CharField(max_length=200, default='ACTIVE')
products = models.ForeignKey(OrderProduct)
client = models.ForeignKey(CustomUser)
And OrderProduct:
class OrderProduct(models.Model):
product = models.ForeignKey(Product)
product_ammount = models.IntegerField()
As you can see, user can add to order different products and different ammount of each product. So, with current models, I can add to order only one type of product. Then I rewrite it in the next way:
class Order(models.Model):
state_choices = ('ACTIVE', 'COMPLETED', 'FROZEN')
order_date = models.DateTimeField(auto_now_add=True)
delivery_time = models.CharField(max_length=100)
address_city = models.CharField(max_length=40)
address_street = models.CharField(max_length=40)
address_building = models.CharField(max_length=40)
state = models.CharField(max_length=200, default='ACTIVE')
client = models.ForeignKey(CustomUser)
class OrderProduct(models.Model):
product = models.ForeignKey(Product)
order = models.ForeignKey(Order)
product_ammount = models.IntegerField()
And in a view, when I need to get a user's orders, I just do next: Order.objects.get(client=request.user).orderproduct_set
But I think that it's not correct. How to rebuild these models to gain the desired result?
In my opinion the second approach is perfectly fine.
One small error in the question is that the query uses get() instead of filter(). This will lead to an exception once one user has more than one order.
So, instead of the get() it would be:
orders = Order.objects.filter(client=request.user)
for order in orders:
print order.orderproduct_set.all()
To use this in a template (question from the comments) it is enough to pass the orders:
views.py
class MyView(View):
def get(self, request):
ctx = {
'orders': Order.objects.filter(client=request.user)
}
return render(request, 'my/template.html', ctx)
my/template.html
{% for order in orders %}
{% for item in order.orderproduct_set.all %}
{{ item.product_amount }}x {{ item.product }}<br/>
{% endfor %}
{% endfor %}