Foreign-key automatically fill in the form - django

I have four models of the shop, customer, product, an order.
I am showing the relation of models
shop
user = models.OneToOneField(User, null=True, related_name='shop', blank=True, on_delete=models.CASCADE)
customer
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE
product
shop = models.ForeignKey(Shop, models.CASCADE, null=True, blank=True)
order
shop = models.ForeignKey(Shop, models.CASCADE, null=True)
customer = models.ForeignKey(Customer, models.CASCADE, null=True)
product = models.ForeignKey(Product, models.CASCADE, null=True)
when customers login then the shop will print on the screen and a button on shop to show the products by the shop in the form card
how I can create an order form so that the customer in order is the instance and the shop in order is that shop which is selected to show the products and every card of the product have a field to fill the remaining detail and submit

If I was you, I would add it to the form, with one of the following two options
Option 1
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Get the shop_id from the URL
shop = get_object_or_404(Shop, pk=shop_id)
# Create a form and add it the context
form = OrderForm(
# This is where the magic happens
initial={"shop": shop], "customer": self.request.user})
# Use this if you want to hide it as well
form.fields['item'].widget = forms.HiddenInput()
Option 2
# If the form is already created you can use this pattern
context['form'].fields['shop'].initial = shop
context['form'].fields['customer'].initial = self.request.user

Related

Filter queryset of django inline formset based on attribute of through model

I have a basic restaurant inventory tracking app that allows the user to create ingredients, menus, and items on the menus. For each item on a given menu, the user can list the required ingredients for that item along with a quantity required per ingredient for that item.
Menu items have a many-to-many relationship with ingredients, and are connected via an "IngredientQuantity" through table.
Here are my models:
class Ingredient(models.Model):
GRAM = 'Grams'
OUNCE = 'Ounces'
PIECE = 'Pieces'
UNIT_CHOICES = [
('Grams', 'Grams'),
('Ounces', 'Ounces'),
('Pieces', 'Pieces')
]
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=200)
unitType = models.CharField(max_length=200, choices=UNIT_CHOICES, verbose_name='Unit')
unitCost = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Unit Cost')
inventoryQuantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Quantity')
def __str__(self):
return self.name + ' (' + self.unitType + ')'
def totalCost(self):
result = self.inventoryQuantity * self.unitCost
return "{:.0f}".format(result)
class Menu(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=200)
timeCreated = models.DateTimeField(auto_now_add=True)
timeUpdated = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.name)
class MenuItem(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
ingredients = models.ManyToManyField(Ingredient, through='IngredientQuantity')
menu = models.ForeignKey(Menu, on_delete=models.CASCADE, null=True)
def __str__(self):
return self.name
def itemCost(self):
relevantIngredients = IngredientQuantity.objects.filter(menuItem=self)
cost = 0
for ingredient in relevantIngredients:
cost += (ingredient.ingredient.unitCost * ingredient.ingredientQuantity)
return cost
class IngredientQuantity(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
menuItem = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
ingredientQuantity = models.IntegerField(default=0)
def __str__(self):
return str(self.ingredient)
This is a multi-user app, so when a user creates a new item on a menu and adds ingredients to it, they should only have the option of choosing ingredients they have created, not those of other users. Here is my attempt to do that in my views:
def ItemUpdate(request, pk):
item = MenuItem.objects.get(id=pk)
user = request.user
IngredientQuantityFormset = inlineformset_factory(
MenuItem, IngredientQuantity, fields=('ingredient', 'ingredientQuantity'), can_delete=True, extra=0
)
form = ItemCreateForm(instance=item)
formset = IngredientQuantityFormset(instance=item, queryset=IngredientQuantity.objects.filter(ingredient__user=user))
if request.method == 'POST':
form = ItemCreateForm(request.POST, instance=item)
formset = IngredientQuantityFormset(request.POST, instance=item, queryset=IngredientQuantity.objects.filter(ingredient__user=user))
# rest of view...
I've searched everywhere for how to implement the queryset parameter properly, but I cannot get it to work. When creating an item on a menu, the user still has the ability to choose from every ingredient in the database (including the ones created by other users). I would like the user to only be able to choose from the ingredients they themselves created.
Does anyone know how to do this properly? Thank you!
I received some guidance on Django forums and arrived at a solution which is documented below:
https://forum.djangoproject.com/t/filter-dropdown-options-in-django-inline-formset-based-on-attribute-of-through-model/13374/3

Django model query with Foreign key and get logged in user

Hi I am trying to add "fields" to my django projects that would be calculated based on query..
Basically I have 2 models one is a user which is an extension of Abstract user
class CustomUser(AbstractUser):
pass
and my main model is Project
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)
this_month = datetime.datetime.now().month
allprojectsthismonth = Project.objects.filter(created__month=this_month)
def __str__(self):
return self.name
I create Project objects via a web 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')
What I need now is to have a queryset that gets me the combination of the total of a given user Project object so that I can make calculations on it, how would I go about doing that?
ideally I would get the logged in user and I could add to my view the sum of all Project.object.total with user = currently logged in.
Thanks
EDIT: Would this work?
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)
this_month = datetime.datetime.now().month
projectsthismonth = self.objects.filter(created__month=this_month)
def monthlyTotal(self,request):
projectsthismonth.objects.filter(
user=request.user
).aggregate(
sum_total=Sum('total')
)['sum_total']
I seem to be missing something however.
You can obtain the queryset of the Projects where the user is the logged in user with:
Project.objects.filter(user=request.user)
If you want to make simple aggregates (like sum, count, etc.), you better use .aggregate(…) [Django-doc] since then the calculations are done at the database level. For example:
from django.db.models import Sum
Project.objects.filter(
user=request.user
).aggregate(
sum_total=Sum('total')
)['sum_total']
will retrieve the sum of the totals of the Projects with user=request.user, this will be None if there are no projects related to the user at all.
from django.utils.timezone import now
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)
def monthlyTotal(self,user):
this_month = now().month
return Project.objects.filter(
created__month=this_month,
user=user
).aggregate(
sum_total=Sum('total')
)['sum_total']
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

best way to use Coupon code in Guest and Login User

In django what is the best way to use coupon code for guest user and login user for per coupon per order?
my order model is like
# models.py
class Orders(models.Model):
order_number = models.AutoField(primary_key=True)
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
ordertime = models.DateTimeField(auto_now_add=True)
customer= models.ForeignKey(Customer, on_delete=models.CASCADE, null=True)
guest =models.CharField(max_length=500, null=True, blank=True)
In my opinion I should create a coupon model like something
class Coupon(models.Model):
coupon = models.charField(max_length=50)
At the end saved in sessions
Set a session value
request.session['order'] = 'coupon_applied'
Delete a session value
del request.session['order']

Django: set model choice field options to queryset values and other value

In the below Django models
class Post(models.Model):
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE,)
title = models.CharField(max_length=200, null=True)
text = models.TextField()
post_url = models.URLField(max_length = 200, blank=True)
post_type = models.IntegerField()
slug = models.SlugField(unique=True, blank=True)
class Tiers(models.Model):
user = models.ForeignKey(CustomUser, default=None, null=True, on_delete=models.CASCADE,)
tier_name = models.CharField(max_length=128, blank=True)
tier_value = models.IntegerField()
slug = models.SlugField(unique=True, blank=True)
I want to use post model for a form like below
class PostForm(forms.ModelForm):
class Meta:
model = models.Post
fields = ('title', 'text', 'post_url', 'post_type')
But for post_type field I want to display as dropdown with options from Tiers models tier_value. For example if user1 has 3 entries in Tiers model with tier_values as 10, 20 and 30. I want to display 4 options 0, 10, 20 , and 30. Can someone help me how to achieve this?
In theory, you can override the FormField django generates. In this case, you could use a Select widget for the post_type field.
However, I think with your modeling can be improved/normalized, and if that is done, your issue will resolve. Consider these models:
class Tier(models.Model):
name = models.CharField(max_length=128, blank=True)
value = models.IntegerField()
slug = models.SlugField(unique=True, blank=True)
class CustomUser(...):
tiers = models.ManyToManyField(Tier, related_name='users')
...
class Post(models.Model):
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
tier = models.ForeignKeyField(Tier)
title = models.CharField(max_length=200, null=True)
text = models.TextField()
url = models.URLField(max_length = 200, blank=True)
slug = models.SlugField(unique=True, blank=True)
This way, you different users can have the same tier (which is probably what you want?) without duplication of the name and level values.
Now, when you create ModelForm with Post, it will automatically give you select field for all existing tiers. However, you just want to be able to select the tiers the user is in, so you would set a custom queryset for that field:
class PostForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['tier'].queryset = Tier.objects.filter(users__in=[user])
class Meta:
model = models.Post
fields = ('title', 'text', 'url', 'tier')
Edit: I just now saw that you want to allow all tiers that the user is in or that have less value. You can do this the same way, you just have to adapt the queryset:
max_tier_value = user.tiers.aggregate(Max('value')).value__max
self.fields['tier'].queryset = Tier.objects.filter(value__lte=max_tier_value)
However, you probably want to do either of these two, but not both:
Each user is assigned with a tier level and can create posts with any lower tier level.
Each user is assigned with multiple tier levels and can only create posts with these.
So, if you go with this queryset, you should remodel so that CustomUser.tier is a models.ForeignKey(Tier, related_name='users')

Django queryset annotate calculated values from another model

I have 4 models in a hierarchy: analysis, books, category and publisher. In my app you analyze books. The books belong to categories where you can view the average analysis rating of all the books in the category. This average rating (that I call avg-catg-rating) is not stored in the dB, it’s calculated in the view.
Here’s my question: how do I get the average category rating for each category that the publisher has onto a single publisher view? In other words, how do I annotate the avg-catg-rating onto each category so I can display all of them on the publisher’s page?
So do I iterate using the category somehow like this question? If yes, please give a tip on how because I'm not really sure how to go about it. I also tried groupby as suggested by this chap, but I could only get the count of book instances, I couldn't accurately annotate the avg-catg-rating.
To clarify:
Models.py:
class Analysis:
title = models.CharField
content_rating_1 = models.IntegerField(blank=True,
null=True, default="0")
content_rating_1_comment = models.TextField(max_length=300, blank=True,
null=True)
source_rating_1 = models.IntegerField(blank=True,
null=True, default="0")
source_rating_1_comment = models.TextField(max_length=300, blank=True,
null=True)
book = models.ForeignKey
class Book:
publication_date = models.DateField(default=datetime.date.today)
slug = models.SlugField(allow_unicode=True, unique=False, max_length=160)
category = models.ForeignKey
class Category:
title = models.CharField(max_length=150, unique=False, blank=False)
sub_title = models.CharField(max_length=100, blank=True, null=True)
publisher = models.ForeignKey
class Publisher:
title = models.CharField(max_length=150, unique=False, blank=False)
publisher/views.py:
class ViewPublisher:
def get_context_data(self, **kwargs):
category_qs = Category.objects.filter(publisher__pk=self.kwargs['pk'])
avg-catg-rating = Books.objects.annotate(long complex queryset that uses values from the analysis model)
context = super(ViewPublisher, self).get_context_data(**kwargs)
context['categories'] = category_qs
context['category_stats'] = Books.objects.filter(category__pk__in=category_qs)\
.annotate(.....and now what???)
I let go of my obsession with doing everything in the view.
The solution is to create a method in your models that you can call in the template. In my case, I created a method in the Category/models.py like so:
class Category:
title = models.CharField(max_length=150, unique=False, blank=False)
sub_title = models.CharField(max_length=100, blank=True, null=True)
publisher = models.ForeignKey
def sum_catg_avg_rating(self):
scar = self.books.annotate(avg-catg-rating=(annotate the AVG calculations from the
analysis))
return scar
Then included the category into the context of the publisher:
class ViewPublisher:
def get_context_data(self, **kwargs):
category_qs = Category.objects.filter(publisher__pk=self.kwargs['pk'])
context = super(ViewPublisher, self).get_context_data(**kwargs)
context['categories'] = category_qs
return context
Now, I can just call it in the template:
{% for catg in categories %}
<h5>{{ catg.title }}</h5>
<p> RATING:<i>{{ catg.sum_catg_avg_rating.avg-catg-rating }}</i></p>