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

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)

Related

How to get the previous and next related post in django?

views.py
def post_details(request,pk):
post = Post.objects.get(id=pk)
# next_post = Post.objects.filter(id=pk)
context={'post':post,'next':next_post}
return render(request, 'blog/post_detail.html', context)
blog-detail
<div class="s-content__pagenav group">
<div class="prev-nav">
<a href="#" rel="prev">
<span>Previous</span>
Tips on Minimalist Design
</a>
</div>
<div class="next-nav">
<a href="#" rel="next">
<span>Next</span>
Less Is More
</a>
</div>
</div>
models
# this is my model
class User(AbstractUser):
# pass
name = models.CharField(max_length=200)
bio = models.TextField(null=True)
email = models.EmailField(unique=True, null=True)
avatar = models.ImageField( null=True, upload_to='blog_media', default="images/avatar.svg")
facebook = models.URLField(blank=True, null=True)
twitter = models.URLField(blank=True, null=True)
dribbble = models.URLField(blank=True, null=True)
instagram = models.URLField(blank=True, null=True)
class Category(models.Model):
name = models.CharField(max_length=20)
class Meta:
verbose_name = 'Category'
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ManyToManyField(Category)
title = models.CharField(max_length=200, blank=False);
description = models.TextField(null=True,blank=True)
image = models.ImageField(upload_to='blog_media')
url = models.URLField(null=True, blank=True)
body = HTMLField()
created = models.DateTimeField(auto_now=True)
updated = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Based on your comments, I'm assuming that you would like to get two related posts that have the same category as the current post.
If I'm correct, then one method you could use is to filter the queryset for the same category belonging to the current post then you could choose the next and previous posts of the current post from the retrieved queryset. For example:
def post_details(request, pk):
current_post = Post.objects.get(pk=pk) # retrieving the current post...
# filtering for related posts only by using the category of the current post
# using -> category_in=post.category.all() since it's a ManyToMany field
related_posts = Post.objects.filter(category_in=current_post.category.all())
# next -> get posts with id greater than the current post id, then get the first instance 'next post'
# previous -> get posts with id less than the current post id, then get the first instance 'previous post'
context = {
'post': current_post,
'next': related_posts.filter(id__gt=current_post.id).order_by('id').first(),
'previous': related_posts.filter(id__lt=current_post.id).order_by('-id').first()
}
return render(request, 'blog/post_detail.html', context)
Ideally, that should work.
A quick recommendation here as well... Instead of using Post.objects.get(pk=pk), I'd suggest using get_object_or_404() as this will handle any potential error that Post.objects.get(pk=pk) will throw. So a small update...
from django.shortcuts import get_object_or_404
def post_details(request, pk):
current_post = get_object_or_404(Post, pk=pk) # retrieving the current post...
# the rest of the code follows...

Rendering a custom tag with a foreign key into templates issues

Hi this is the model I am working with
from django.db import models
from users.models import CustomUser
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)
def __str__(self):
return self.name
The class is populated by an HTML 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')
and so I have added a custom tag into my app which is a function
#register.filter
def monthlyTotal(user):
this_month = now().month
return Project.objects.filter(
created__month=this_month,
user=user
).aggregate(
sum_total=Sum('total')
)['sum_total']
I call the tag like this in template
<p>Total monthly sales = {{ user.username|monthlyTotal }}</p>
however I get an error saying Field ID expected a number but got 'grandmaster' which is the name of my test user who has multiple Project objects.. if I switch to user.id I get no error but it displays None which makes sense because when I look at my project section in admin the field user is populated by the username not the id so there would be no project where user=id
You need to use the user, not the username, so:
<p>Total monthly sales = {{ user|monthlyTotal }}</p>

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.

Django manytomany accessing related column

I'm trying to get my head around how Django understands m2m relationships, in SQL you would just add some joins through the intermediate table.
I have a Container which contains various Samples. A Sample can be spread over various Containers.
So in my container I add a alias samples m2m field (essentially a book mark to the other table).
What I can do is get a single Container and display the form information, I would like to add the Sample columns to the form, if I do this for the samples m2m field it returns a multifield, but how do I access the other related fields through the m2m sample_id >=< container_id ?
class Container(models.Model):
container_id = models.AutoField(primary_key=True)
samples = models.ManyToManyField(Sample, through='JoinSampleContainer', through_fields=('container_id', 'sample_id'), related_name='container')
location_id = models.ForeignKey(Location, db_column='location_id', on_delete = models.PROTECT)
icon_desc = models.ForeignKey(Icon, db_column='icon_desc', null=True, blank=True, default='Box',on_delete = models.PROTECT)
container_name = models.CharField(max_length=50, blank=True, null=True)
container_type = models.CharField(max_length=50, blank=True, null=True)
In my Sample table I add the containers alias to act as a bookmark to the other table
class Sample(models.Model):
sample_id = models.AutoField(primary_key=True)
containers = models.ManyToManyField(Container, through='JoinSampleContainer', through_fields=('sample_id', 'container_id'), related_name='sample')
sample_number = models.IntegerField()
material_type = models.CharField(max_length=200, default='', blank=True, null=True, choices = MATERIALS)
weight = models.DecimalField(max_digits=6, decimal_places=2)
description = models.CharField(max_length=500, default='', blank=True, null=True)
recovery_method = models.CharField(max_length=200, default='', blank=True, null=True, choices = RECOVERY_METHODS)
comments = models.CharField(max_length=1000, default='', blank=True, null=True)
In this case I am managing the through table:
class JoinSampleContainer(models.Model):
id = models.AutoField(primary_key=True)
container_id = models.ForeignKey(Container, db_column='container_id', on_delete = models.PROTECT)
sample_id = models.ForeignKey(Sample, db_column='sample_id', on_delete = models.PROTECT)
So now I want to display the contents of a single container through a form. I have the url's setup to pass the container_id.
# views.py
def containercontents(request, pk):
post = get_object_or_404(Container, pk=pk)
# objects = Container.samples.all()
if request.method == "POST":
form = ContainerContentsForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
#post.user = request.user
#post.datetime = datetime.datetime.now()
post.save()
return redirect('allcontainer')
#, pk=post.pk)
else:
form = ContainerContentsForm(instance=post)
return render(request, 'container/containercontents.html', {'form': form})
The Form
# form.py
class ContainerContentsForm(forms.ModelForm):
class Meta:
model = Container
fields = (
'location_id',
'container_name',
'container_type',
'icon_desc',
'samples',
)
N.B. The samples seems to list everything regardless of the container.
Then the html
# html
contents
which passes to:
# html
<div class="">
{{ form }}
</div>
Your models are defined wrongly: You should not define the ManyToManyField on both models, only on one of them. So remove the containers field on Sample and only keep it on Container. Set the related_name to "containers" (plural). That way the relationship Container -> Sample is container.samples.all() and the reverse one is sample.containers.all().
Now the purpose of a form is to allow you to select which Samples you want to associate to a Container. So by default the field will be represented by a ModelMultipleChoiceField. The already associated Samples should be pre-selected when you initialise the form with a Container instance.
You can narrow the samples to select from by specifying the queryset for the field, by overriding the default field in the form:
class ContainerContentsForm(forms.ModelForm):
class Meta:
# same code here
samples = forms.ModelMultipleChoiceField(
queryset = Sample.objects.filter(...)
)
You say you want to "display the contents of a Container through the form", if you just want to display, why use a form? To just display the contents, loop through the related samples and display them:
{% for sample in form.instance.samples.all %}
{{ sample.sample_id }}
{% endfor %}
Note: You should rename your ids to id. sample.sample_id is bad programming style. But I already told you that.

Django filter foreign key model in views.py

I have two models, one is called Books and BookInstance, one Book has many BookInstances,
class Books(models.Model):
.........
def get_absolute_url(self):
"""
Returns the url to access a detail record for this book.
"""
return reverse('book-detail', args=[str(self.id)])
def __str__(self):
"""
String for representing the Model object.
"""
return '{0}'.format(self.book_name)
class BookInstance(models.Model):
books = models.ForeignKey('Books',verbose_name="Books", on_delete=models.SET_NULL, null=True)
keyrequest = models.OneToOneField('BookRequest', verbose_name='Book requests', on_delete=models.SET_NULL, null=True, blank=True,)
LOAN_STATUS = (
('a', 'Available'),
('o', 'On loan'),
('r', 'Reserved'),
)
status = models.CharField(max_length=1, choices=LOAN_STATUS, help_text='Key availability', verbose_name="Key status", blank=True)
date_out = models.DateField(null=True, blank=True, verbose_name="Date Issued")
due_back = models.DateField(null=True, blank=True, verbose_name="Date to be returned")
......
id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text="Unique ID for this particular book")
I have a class based view in views.py that uses the Book model to show the total number of BookInstances for that book, Here's my views.py:
class KeyListView(generic.ListView):
model = RoomKey
fields = '__all__'
template_name = 'catalog/roomkey_list.html'
And I have a template that shows the number of all BookInstances for a Book,as shown below:
{{ books.bookinstance_set.all.count }}
But I would like to filter it out and show the number of available BookInstance of that Book, I tried to use add a Query manager in the BookInstance class but that didn't work , django never threw any error it just didn't show anything. Can someone please tell me the correct way to implement something like this?
You can override view's get_queryset() method, and annotate count:
from django.db.models import Count, Case, When, CharField
def get_queryset(self):
return Books.objects.annotate(
available_books_count=Count(Case(
When(bookinstance__status='a', then=1),
output_field=CharField(),
))
Now in template you can do
{{ books.available_books_count }}