Django forms.ChoiceField to be dynamically created based - python

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})

Related

How can I do username in reviews on django?

I'm doing reviews on django, but I want the user to not be able to enter any name. I want the username in the reviews to match the username of his profile
models.py
class Reviews(models.Model):
name = models.CharField('Имя', max_length=100)
text = models.TextField('Отзыв', max_length=3400)
parent = models.ForeignKey('self', verbose_name='Родитель', on_delete=models.SET_NULL, null=True, blank=True)
book = models.ForeignKey(BookModel, verbose_name='книга', on_delete=models.CASCADE)
name_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
views.py
class MoreInfoView(View):
""" """
def get(self, request, id):
book_info = BookModel.objects.filter(id=id).first()
stuff = get_object_or_404(BookModel, id=self.kwargs['id'])
total_likes = stuff.total_likes()
return render(request, 'bookapp/more_info.html', context={
'id': id,
'book_info': book_info,
'book': BookModel.objects.all(),
'total_likes': total_likes,
})
class AddReview(View):
"""Add Review"""
def post(self, request, pk):
form = ReviewForm(request.POST)
book = BookModel.objects.get(id=pk)
if form.is_valid():
form = form.save(commit=False)
form.book = book
form.save()
return HttpResponseRedirect(reverse('more_info', args=[pk]))
forms
class ReviewForm(forms.ModelForm):
class Meta:
model = Reviews
fields = ("name", "text", 'name_user')
You can add user manually after validating ReviewForm
I also added some changes(suggestions)
models.py
class Reviews(models.Model):
name = models.CharField('Имя', max_length=100)
text = models.TextField('Отзыв', max_length=3400)
parent = models.ForeignKey('self', verbose_name='Родитель', on_delete=models.SET_NULL, null=True, blank=True)
book = models.ForeignKey(BookModel, verbose_name='книга', on_delete=models.CASCADE, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
Setting blank=True makes the field optional.
views.py
class MoreInfoView(View):
""" """
def get(self, request, id):
book_info = BookModel.objects.filter(id=id).first()
stuff = get_object_or_404(BookModel, id=self.kwargs['id'])
total_likes = stuff.total_likes()
return render(request, 'bookapp/more_info.html', context={
'id': id,
'book_info': book_info,
'book': BookModel.objects.all(),
'total_likes': total_likes,
})
class AddReview(View):
"""Add Review"""
def post(self, request, pk):
user = request.user
# User has to be authenticated to create a review. And backend must
# validate it. You should raise PermissionDenied as response or
# redirect user to the login page, or something similar.
if not request.user.is_authenticated:
raise PermissionDenied()
form = ReviewForm(request.POST)
book = BookModel.objects.get(id=pk)
if form.is_valid():
form = form.save(commit=False)
form.book = book
form.user = user
form.save()
return HttpResponseRedirect(reverse('more_info', args=[pk]))
forms.py
class ReviewForm(forms.ModelForm):
class Meta:
model = Reviews
fields = ("name", "text")
I would advise to work with a CreateView [Django-doc] that will simplify a lot of the logic. You can implement this as:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView
class AddReviewView(LoginRequiredMixin, CreateView):
form_class = ReviewForm
def get_success_url(self):
return reverse('more_info', args=[self.kwargs['pk']])
def form_valid(self, form):
form.instance.book_id = self.kwargs['pk']
form.name_user = self.request.user
return super().form_valid(form)
In your ReviewForm you thus remove the name_user as fields element.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].
Note: normally a Django model is given a singular name, so Review instead of Reviews.
Note: Models normally have no …Model suffix. Therefore it might be better to rename BookModel to Book.

how to populate user field with current user in django models via forms?

hi i am working on a django app. functionality that i am implementing is to let my user buy a internet pack from the website.
i have implemented the model, view, template and url so far. but in the form i am getting a drop down list of all the users registered on the app. i automatically want django to link the user with current logged in user and let him select the pack he wants to buy and populate the model(table) automatically.
My models.py
def get_deadline():
return dt.today() + timedelta(days=30)
class CustomUser(AbstractUser):
Address = models.CharField(max_length=500)
def __str__(self):
return self.username
class Plans(models.Model):
plan_name = models.CharField(max_length=50)
speed = models.IntegerField()
price = models.FloatField()
def __str__(self):
return self.plan_name
class Orders(models.Model):
user = models.ForeignKey(CustomUser, on_delete = models.CASCADE)
pack = models.ForeignKey(Plans, on_delete = models.CASCADE)
start_date = models.DateField(auto_now_add=True)
end_date = models.DateField(default=get_deadline())
is_active = models.BooleanField(default=True)
def __str__(self):
name = str(self.user.username)
return name
my views.py
class UserBuyPlan(LoginRequiredMixin, View):
template = 'plans/plan.html'
#success_url = reverse_lazy('autos:all')
success_url = reverse_lazy('home-home')
def get(self, request):
form = BuyPlanForm()
ctx = {'form': form}
return render(request, self.template, ctx)
def post(self, request):
form = BuyPlanForm(request.CustomUser,request.POST)
if not form.is_valid():
ctx = {'form': form}
return render(request, self.template, ctx)
make = form.save()
return redirect(self.success_url)
my forms.py (i tried searching online and found this init implementation but it doesnt work)
class BuyPlanForm(forms.ModelForm):
class Meta():
model = Orders
fields = "__all__"
def __init__(self, *args, **kwargs):
self.user = CustomUser
super(BuyPlanForm, self).__init__(*args, *kwargs)
self.fields['user'].initial = self.user
the photo of resulting form is attached below
ok so i found the answer.
just had to change my view function a bit.
if you want a detailed tutorial then please visit https://www.youtube.com/watch?v=-s7e_Fy6NRU&t=1840s
he explains in a much better way.
class UserBuyPlan(LoginRequiredMixin, CreateView):
model = Orders
template_name = 'plans/plan.html'
fields = ['pack']
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
and also had to add absolute url method to my orders model
class Orders(models.Model):
user = models.ForeignKey(CustomUser, on_delete = models.CASCADE)
pack = models.ForeignKey(Plans, on_delete = models.CASCADE)
start_date = models.DateField(auto_now_add=True)
end_date = models.DateField(default=get_deadline())
is_active = models.BooleanField(default=True)
def __str__(self):
name = str(self.user.username)
return name
def get_absolute_url(self):
return reverse('home-home')
rest everything is same.

Django - Pass two Models into one view, and display both models

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

Django access ForeignKey field and get their values

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')

How do I pass only current user data to be filtered in django-filter?

I am using django-filter to filter my data. The issue is both the available options of the filters and the results are based on all the data, not only on the curent user.
models.py
class Product(models.Model):
title = models.CharField(max_length=264)
description = models.CharField(max_length=264)
date_added = models.DateField(auto_now_add=True)
time_added = models.TimeField(auto_now_add=True)
user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
I used the code below to filter the results only on the current user:
filters.py
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
#property
def qs(self):
parent = super(ProductFilter, self).qs
user = getattr(self.request, "user", None)
return parent.filter(user=user).order_by("-timestamp")
In my views.py I tried to pass the queryset, but nothing changed:
class ProductsFilterView(LoginRequiredMixin, FilterView):
model = Product
filterset_class = ProductFilter
def get_queryset(self, **kwargs):
return self.model.objects.filter(user=self.request.user)
I am not sure how to limit the available filter options only on the current user's data.
So, summarizing how can I pass into the filterset just the current user's data and not pass the whole data into the filterset and then filter them on current user's data.
filters.py
class ProductFilter(django_filters.FilterSet):
class Meta:
title = django_filters.CharFilter(lookup_expr='icontains')
# And you can also do "lookup_expr='year__gt' and also 'year__lt'"
date_added = django_filters.NumberFilter(name='date_added', lookup_expr='year')
class Meta:
model = Product
fields = [] #You can add fields that wants to be displayed
views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from .filters import ProductFilter
# Function Base View
#login_required
def productfilter(request):
user = request.user
product_list = Product.objects.filter(user=user)
product_filter = ProductFilter(request.GET, queryset=product_list)
return render(request, "template_name.html", {'products': product_list})
#Class Based View
class ProductsFilterView(LoginRequiredMixin, FilterView):
context_object_name = 'products'
template_name = 'template_name.html'
filter_set = ProductFilter
def get_queryset(self):
return Product.objects.filter(user=self.request.user)
I hope this was helpful.

Categories