I`m trying to build a small webshop platform where a user can create a shop, select a category of products and add products to it.
To achieve my goal I created this simplified models.py
class Organization(models.Model):
org_id = models.AutoField(primary_key=True)
company_name = models.CharField(max_length=255)
owned_by = models.OneToOneField(UserProfile, on_delete=models.CASCADE)
def __str__(self):
return f'{self.company_name} ORG'
def get_absolute_url(self):
return reverse('org-view', kwargs={'pk': self.org_id})
class Category(models.Model):
CATEGORIES = ( ('electric', 'Electronics'), ('food', 'FrozenFood'), ('shoes', 'slippers') )
cat_name = models.CharField(max_length=255, choices=CATEGORIES)
def __str__(self):
return f'{self.cat_name} Category'
def get_absolute_url(self):
return reverse('cat-view', kwargs={'id': self.pk})
class Product(models.Model):
org_id = models.ForeignKey(Organization, on_delete=models.CASCADE, blank=True, null=True)
cat_name = models.ForeignKey(Category, on_delete=models.CASCADE)
product_name = models.CharField(max_length=255)
def __str__(self):
return self.product_name
In my views i want to keep the user on a single page where he can manage his shop.
My current views.py:
class OrganizationDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView, FormMixin):
model = Organization
queryset = Organization.objects.all()
template_name = 'org/org_view.html'
form_class = ProductForm
def test_func(self):
org = self.get_object()
if self.request.user.profile == org.owned_by:
return True
return False
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data()
pk = self.object.serializable_value('pk')
product = Product.objects.filter(org_id=pk)
return self.render_to_response(context)
I need help to understand a few things:
how to execute the queries to retrieve all of his products and be able to see the category in which the product belongs.
My bad solution: the product variable holds all the products for that shop, including a cat_name_id that points to the category pk. Thats nice, but i need the name of the category.
i need something like this: print(product.category_name) and i should see 'Electronics'
how to execute a query that groups together items under the same category.
I already spent days trying to understand those queries, so please Explain like i`m five
You can filter all products of one user with the following command:
products = Product.objects.all().filter(org_id__owned_by=user)
See the documentation: https://docs.djangoproject.com/en/3.0/topics/db/queries/#retrieving-specific-objects-with-filters. Here, 'user' is the user object from your User_Profile class.
With respect to your second question, I'm guessing that you want to order a specific query by category; in that case the statement would be:
products = Product.objects.order_by('cat_name')
Or you can combine the two with:
products = Product.objects.all().filter(org_id__owned_by=user).order_by('cat_name')
Related
I'm trying to pass two models into a create view, where i am trying to get the the primary key from the URL to retrieve the details from a food truck model so it can be displayed in the page, and where a user can write a review about food truck. Also, I'd like a list of the reviews to be displayed on the page.
views.py
class TruckReviewView(CreateView):
model = Review
template_name = 'truckReviews/detail.html'
fields = ['speedOfService', 'qualityAndTaste', 'valueForMoney', 'comment']
def get_queryset(self):
self.pk = self.kwargs['pk']
queryset = super(TruckReviewView, self).get_queryset()
return queryset
def get_context_data(self, **kwargs):
context = super(TruckReviewView, self).get_context_data(**kwargs)
context['truck'] = FoodTrucks.objects.get(truckID=get_queryset())
context['reviews'] = Review.objects.get(truckID=get_queryset())
return context
urls.py
urlpatterns = [
path('', TruckListView.as_view(), name='reviews-home'),
path('truck/<int:pk>/', TruckReviewView.as_view(), name='truck-detail'),
path('about/', About.as_view(), name='reviews-about'),
]
models.py
class FoodTrucks(models.Model):
truckID = models.IntegerField(primary_key=True, unique=True, null=False)
name = models.CharField(max_length=25)
category = models.CharField(max_length=20)
bio = models.TextField()
avatarSRC = models.TextField(default=None)
avatarALT = models.CharField(max_length=20, default=None)
avatarTitle = models.CharField(max_length=20, default=None)
coverPhotoSRC = models.TextField(default=None)
coverPhotoALT = models.CharField(max_length=20, default=None)
coverPhotoTitle = models.CharField(max_length=20, default=None)
website = models.TextField(default=None)
facebook = models.CharField(max_length=100, default=None)
instagram = models.CharField(max_length=30, default=None)
twitter = models.CharField(max_length=15, default=None)
class Review(models.Model):
reviewID = models.AutoField(primary_key=True, unique=True, serialize=False, null=False)
truckID = models.ForeignKey(FoodTrucks, on_delete=models.CASCADE)
userID = models.ForeignKey(User, on_delete=models.CASCADE)
datePosted = models.DateTimeField(default=timezone.now)
speedOfService = models.IntegerField()
qualityAndTaste = models.IntegerField()
valueForMoney = models.IntegerField()
comment = models.TextField(max_length=128)
I've tried to use get_queryset to get the pk from the URL and pass the pk into get_context_data and target the specific truck with that ID in the database.
The difficulty comes from the fact that you combine a list view and create view. If you want to combine this into one view, then you need to do a bit mixing and matching with different mixins of the Class Based Views.
It can be done, but it's not trivial. If you're new to Django, then this may be overshooting things. I've renamed fields an such and did it as an exercise. I haven't bothered with the form submission, it shouldn't be that difficult to implement as the other views don't deal with the methods involved (form_valid, get_success_url, etc). You can use it as a guide to see what you should be learning. The above linked site is extremely convenient to see how things are mixed together.
The result below will provide the variables "foodtruck", "reviews" and "form" to the template.
import typing as t
from django.views import generic
from .models import FoodTruck, Review
from .forms import ReviewForm
if t.TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse
from django.contrib.auth.models import AbstractUser
class AuthenticatedRequest(HttpRequest):
user: AbstractUser = ...
class FoodTruckDetailReviewListCreateView(
generic.list.MultipleObjectMixin, generic.edit.CreateView,
):
template_name = "foodtrucks/detail.html"
model = Review
list_model = Review
context_list_name = "reviews"
context_object_name = "foodtruck"
detail_model = FoodTruck
form_class = ReviewForm
def get(self, request: "AuthenticatedRequest", *args, **kwargs) -> "HttpResponse":
"""
Combine the work of BaseListView and BaseDetailView
Combines the get implementation of BaseListView and BaseDetailView, but
without the response rendering. Then hands over control to CreateView's
method to do the final rendering.
Some functionality is stripped, because we don't need it.
:param request: The incoming request
:return: A response, which can be a redirect
"""
# BaseListView
self.object_list = self.get_queryset()
# BaseDetailView
self.object = self.get_object()
context = self.get_context_data(
object=self.object, object_list=self.object_list
)
# CreateView sets self.object to None, but we override form_kwargs, so
# we can leave it at a value.
return self.render_to_response(context=context)
def get_template_names(self):
# Bypass logic in superclasses that we don't need
return [self.template_name]
def get_object(self, queryset=None):
# We provide the queryset to superclasses with the other model
return super().get_object(queryset=self.detail_model.objects.all())
def get_queryset(self):
# This only gets called by MultipleObjectMixin
pk = self.kwargs.get(self.pk_url_kwarg)
if pk is None:
raise AttributeError(
"Unable to filter on food truck: {} is missing in url.".format(
self.pk_url_kwarg
)
)
queryset = self.list_model.objects.filter(food_truck_id=pk)
# print(str(queryset.query))
return queryset
def get_context_data(self, **kwargs):
if "object" in kwargs:
kwargs[self.context_object_name] = kwargs["object"]
if "object_list" in kwargs:
kwargs[self.context_list_name] = kwargs["object_list"]
return super().get_context_data(**kwargs)
def get_form_kwargs(self):
# Bypass ModelFormMixin, which passes in self.object as instance if it
# is set.
return super(generic.edit.ModelFormMixin, self).get_form_kwargs()
And as a reference, this is what I changed the models to:
import uuid
from django.contrib.auth import get_user_model
from django.db import models
from django.utils import timezone
class FoodTruck(models.Model):
name = models.CharField(max_length=25)
category = models.CharField(max_length=20)
bio = models.TextField()
avatar_url = models.URLField(blank=True)
avatar_alt_text = models.CharField(max_length=20, blank=True)
avatar_title = models.CharField(max_length=20, blank=True)
cover_photo_url = models.URLField(blank=True)
cover_photo_alt_text = models.CharField(max_length=20, default="No photo provided")
cover_photo_title = models.CharField(max_length=20, default="No photo provided")
website = models.URLField(blank=True)
facebook = models.CharField(max_length=100, blank=True)
instagram = models.CharField(max_length=30, blank=True)
# https://9to5mac.com/2017/11/10/twitter-display-name-limit/
twitter = models.CharField(max_length=50, blank=True)
def __str__(self):
return self.name
class Review(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
food_truck = models.ForeignKey(
FoodTruck, on_delete=models.CASCADE, related_name="reviews"
)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
posted_at = models.DateTimeField(default=timezone.now)
speed_of_service = models.IntegerField()
quality_and_taste = models.IntegerField()
value_for_money = models.IntegerField()
comment = models.TextField(max_length=128)
def __str__(self):
return "Review about {} by {}".format(
self.food_truck.name, self.user.get_full_name()
)
And finally the form (with a bit of trickery to inject bootstrap classes):
class ReviewForm(forms.ModelForm):
def __init__(self, **kwargs):
super().__init__(**kwargs)
for field in self.fields.values():
if not field.widget.is_hidden:
field.widget.attrs.setdefault("class", "form-control")
class Meta:
model = Review
exclude = ("uuid", "user", "food_truck", "posted_at")
First, there's not need to create a truckID and reviewID primary key fields because Django creates a unique id field for each object automatically on which you can simply do .get(id=1) or .filter(id=1) etc.
Just like it is completely useless to put ID in fields with Foreign Key or any relational fields because Django will automatically take the name and append _id to it. For instance, just user would become user_id or truck would be truck_id in backend on which you can do .get(user__id=1) or .get(user_id=1) for example.
You should review this section of your code. You're actually not doing anything with the primary key:
def get_queryset(self):
queryset = super().get_queryset()
try:
item = queryset.get(id=self.kwargs['pk'])
except:
...
else:
# Do something with item here
...
finally:
return queryset
or, with get_context_data:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
queryset = super().get_queryset()
try:
item = queryset.get(id=kwargs['pk'])
except:
...
else:
# Do something with item here
context['item'] = item
finally:
return context
I'm sorry but your question is a bit confusing. If you're trying to get details from a model you should use a DetailView. Also, on a DetailView assuming you want the details of a Review since you have the truck on a review you could simply override the get_context_data and set truck in the context by doing self.object.truck.
If you're trying to create a review then it's right to use the CreateView but that should only be for the Review model.
To list you should use a ListView.
So, to my understanding, you have a truckID and want to create a review for that. In that case, it'd have a CreateView for Review model.
Have a look at the CreateView, DetailView and ListView docs
Im building a website with django where a user could create a shop > choose maximum one of each category > choose multiple products for each category; in that order
I have a DetailView which allows them to see the details of the shop and create a category underneath it.
I`m using FormMixin to allow them create a category, and a ChoiceField for the type of category.
What I want:
Let them choose only from the categories they didnt choose in the past,
eg: I have 3 categories: Woodworks, Food, Tools; if they already have added a Tools category to their shop, they should be able to choose it again (i want the form not display it at all)
My views:
class OrganizationDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView, FormMixin):
model = Organization
queryset = Organization.objects.all().prefetch_related()
template_name = 'org/org_view.html'
form_class = CategoryForm
def test_func(self):
org = self.get_object()
if self.request.user.profile == org.owned_by:
return True
return False
def get(self, request, *args, **kwargs):
# getting the post to display
self.object = self.get_object()
context = self.get_context_data()
categories = Category.objects.filter(org=self.object).values_list('cat_name')
context['categories'] = []
for category in categories:
context['categories'].append(*category)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
pk = self.get_object().serializable_value('pk')
org_id = Organization.objects.get(org_id=pk)
cat_name = self.request.POST.get('cat_name')
new_category = Category.objects.create(org=org_id, cat_name=cat_name)
return self.get(request)
My forms.py:
class CategoryForm(forms.ModelForm):
CATEGORIES = ( ('WOOD', 'WoodWorks'), ('FOOD', 'Food'), ('TOOLS', 'Tools') )
cat_name = forms.ChoiceField(choices=CATEGORIES)
class Meta:
model = Category
fields = [ 'cat_name' ]
Not sure if the models.py is relevent here.
Added models.py for the category
class Category(models.Model):
CATEGORIES = ( ('WOOD', 'WoodWorks'), ('FOOD', 'Food'), ('TOOLS', 'Tools') )
org = models.ForeignKey(Organization, on_delete=models.CASCADE)
cat_name = models.CharField(max_length=255, choices=CATEGORIES)
def __str__(self):
return f'{self.cat_name} Category'
def get_absolute_url(self):
return reverse('cat-view', kwargs={'id': self.pk})
List page is paginated and displaying only 10 journal posts. Despite this, there are 95 queries being performed. It's affecting the page load speed. Instead of loading <1 second it's taking around 4-5 seconds.
Here are my code, please check and let me know how to optimize.
views.py
class PostListView(LoginRequiredMixin,ListView):
model = Post
paginate_by = 10
def get_queryset(self):
qs = super(PostListView, self).get_queryset().filter(Q(
Q(language_id__in=self.request.user.native_language),
~Q(user_id__in=self.request.user.forbidden_user_list))).order_by('-created')
return qs
def get_context_data(self, **kwargs):
context = super(PostListView, self).get_context_data(**kwargs)
context['list_type'] = 'all'
models.py
class Post(models.Model):
user = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=60)
text = models.TextField()
native_text = models.TextField(blank=True)
created = models.DateTimeField(default=timezone.now, db_index=True)
# updated = models.DateTimeField(auto_now=True)
# is_corrected = models.BooleanField(default=False)
users_like = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='posts_liked',
blank=True)
language = models.ForeignKey('account.Language', on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.text
def get_absolute_url(self):
return reverse('post-detail', args=[str(self.id)])
#property
def get_correctors(self):
from account.models import Profile as User
result = list(set(User.objects.all().filter((Q(correctedrows__post_id=self.id) | Q(perfectrows__post_id=self.id) | Q(comment__post_id=self.id)))))
return result
In your get_queryset method, you should try to use select_related and prefetch_related to get whatever you will use in your template.
For example, if in your template you are doing post.user.name, that is going to cause an additional query. Try something like this:
def get_queryset(self):
qs = super(PostListView, self).get_queryset().select_related('user', 'language'). prefetch_related('users_like').filter(Q(
Q(language_id__in=self.request.user.native_language),
~Q(user_id__in=self.request.user.forbidden_user_list))).order_by('-created')
return qs
I am implementing an app in which a User can create a Shopping List and can search for a Product and add the Product to the Shopping List.
I am stuck at the 'how to add to the list' part. I have created the Model as follows:
class Product(models.Model):
pid = models.IntegerField(primary_key=True)
name = models.CharField(max_length=100, db_index=True)
description = models.TextField(blank=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('shop:product_detail', args=[self.pid, self.slug])
class ShoppingList(models.Model):
user = models.ForeignKey(User, related_name='shoplist', on_delete=models.CASCADE)
list_name = models.CharField(max_length=20)
items = models.ManyToManyField(Product)
slug = models.SlugField(max_length=150, db_index=True)
def __str__(self):
return self.list_name
def get_absolute_url(self):
return reverse('shop:item_list', args=[self.slug])
In the Template to view each Product, I have the following
<h3>{{ product.name }}</h3>
ADD
I need the Particular product which I am displaying through the get_absolute_url, to be Added to the Shopping List, when the ADD button is cliked.
I am lost as to what Steps to take next. Please help.
At first create a model for your wishlist like below
class Wishlist(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)# here CASCADE is the behavior to adopt when the referenced object(because it is a foreign key) is deleted. it is not specific to django,this is an sql standard.
wished_item = models.ForeignKey(Item,on_delete=models.CASCADE)
slug = models.CharField(max_length=30,null=True,blank=True)
added_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.wished_item.title
then create a function for adding the item in the wishlist.... i have tryid this way...
#login_required
def add_to_wishlist(request,slug):
item = get_object_or_404(Item,slug=slug)
wished_item,created = Wishlist.objects.get_or_create(wished_item=item,
slug = item.slug,
user = request.user,
)
messages.info(request,'The item was added to your wishlist')
return redirect('core:product_detail',slug=slug)
You need to create a new view to add the product to the ShoppingList.
and link it using URL
Example:
Template
ADD
urls.py
path("cart/add-product/<int:pk>/", ProductAddView.as_view(), name="add_product"),
I am trying to create a detail-list of my actor where it will show all the shows he has been a cast member of. This is part of mozilla's challenge yourself section at the end of the tutorial.
I am having trouble filtering my class Cast so that I can get a specific Actor.
I do not understand why the filter is not working. If self.object has a value of '3' for example, it should be filtering out all of the actors and only display the actor with the id of 3. But that does not seem to be the case. I also am not understanding the error code it is tossing out. My Cast class does have a foreignkey to person.
Similar to my show details page, instead of casts, I want it to be the actor's starred in movies.
Image of my Show Details Page
View
class ActorDetailView(generic.DetailView):
model = Cast
template_name = 'show/actor-detail.html'
def get_context_data(self, **kwargs):
context = super(ActorDetailView, self).get_context_data(**kwargs)
context['casts'] = Cast.objects.filter(person_id=self.object)
return context
Models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Character(models.Model):
name = models.CharField(max_length=128)
on_which_show = models.ForeignKey('Show', on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.name
class Cast(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
cast_show = models.ForeignKey('Show',on_delete=models.CASCADE)
character = models.ForeignKey(Character, on_delete=models.CASCADE, null=True)
def get_absolute_url(self):
return reverse('actor-detail', args=[str(self.id)])
class Show(models.Model):
title = models.CharField(max_length=200)
genre = models.ManyToManyField(Genre, help_text='Select a genre for this book')
language = models.ForeignKey('Language', on_delete=models.SET_NULL, null=True)
summary = models.TextField(max_length=1000, help_text='Enter a brief description of the show', null=True)
cast_of_the_show = models.ManyToManyField(Person,through='Cast')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('show-detail', args=[str(self.id)])
The model for your ActorDetailView should be Person instead of Cast. Then you can use the person record to get all of the casts they belong to.
class ActorDetailView(generic.DetailView):
model = Person
template_name = 'show/actor-detail.html'
def get_context_data(self, **kwargs):
context = super(ActorDetailView, self).get_context_data(**kwargs)
context['casts'] = Cast.objects.filter(person=self.object)
return context