Limit number of relationship in ManyToManyField in django - django

I am using ManyToMany relationship in my code in a scenario where i have showrooms and their categories. Now a showroom can fall into maximum three categories and i have to validate it while saving. Below is my code:
##models.py
class Showrooms(models.Model):
name = models.CharField(max_length = 100)
contact_person = models.CharField(max_length = 100)
offer = models.TextField()
categories = models.ManyToManyField(Categories, null=True, blank=True, related_name='categories')
class Meta:
db_table = 'showrooms'
verbose_name_plural = "showrooms"
class Categories(models.Model):
category = models.CharField(max_length = 100)
image = models.ImageField(upload_to = showroom_upload_path, null=True, blank=True)
slug = models.SlugField(blank=True)
class Meta:
db_table = 'showroom_categories'
verbose_name_plural = "categories"
def __str__(self):
return self.category
everything is working fine except i am not able to put validation on number of categories per showroom. And i am not using it in views but just wanna to do it in admin.
Please help
Thanks
Edits
Ok.. I have solved my issue. I created a form in forms.py
class ShowroomsForm(forms.ModelForm):
class Meta:
model = Showrooms
def clean(self):
categories = self.cleaned_data.get('categories')
if categories and categories.count() > 3:
raise ValidationError('Maximum three categories are allowed.')
return self.cleaned_data
and added it to admin.py like below:
class ShowroomsAdmin(admin.ModelAdmin):
form = ShowroomsForm
admin.site.register(Showrooms, ShowroomsAdmin)

You could define a clean() method on your model an raise a validation error whenever a showroom gets assigned to more than 3 categories.
https://docs.djangoproject.com/en/1.5/ref/models/instances/#django.db.models.Model.clean

I had the same problem and used #Rakesh Kumar's solution. But then I got an error
django.core.exceptions.ImproperlyConfigured: Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is prohibited; form JobsForm needs updating.
The problem was that Rashid's form didn't have any form fields.
So I modified his solution just a little.
forms.py - I added fields
class ShowroomsForm(forms.ModelForm):
class Meta:
model = Showrooms
fields = "__all__"
def clean(self):
categories = self.cleaned_data.get('categories')
if categories and categories.count() > 3:
raise ValidationError('Maximum three categories are allowed.')
return self.cleaned_data
admin.py - this remained the same:
class ShowroomsAdmin(admin.ModelAdmin):
form = ShowroomsForm
admin.site.register(Showrooms, ShowroomsAdmin)
After that, it worked perfectly!

All I needed was to create a model form:
class ShowroomsForm(forms.ModelForm):
class Meta:
model = Showrooms
def clean(self):
categories = self.cleaned_data.get('categories')
if categories and categories.count() > 3:
raise ValidationError('Maximum three categories are allowed.')
return self.cleaned_data
and added it to admin.py like below:
class ShowroomsAdmin(admin.ModelAdmin):
form = ShowroomsForm
admin.site.register(Showrooms, ShowroomsAdmin)

Related

Cannot assign "id": "Product.category" must be a "CategoryProduct" instance

i'm working on a django project and i got this error (Cannot assign "'11'": "Product.category" must be a "CategoryProduct" instance.) anyone here can help me please.
Model:
class Product(models.Model):
name = models.CharField("Nombre", max_length=150)
category = models.ForeignKey(CategoryProduct, on_delete=models.SET_NULL, null=True, related_name='category')
def __str__(self):
return self.name
View:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
def post(self, request, *args, **kwargs):
form = self.get_form()
category = CategoryProduct.objects.get(id=request.POST['category'])
if form.is_valid():
product = form.save(commit=False)
product.category = category
product.save()
Form:
class ProductForm(forms.ModelForm):
name = forms.CharField(max_length=150, label="Nombre")
category = forms.ChoiceField(choices=[(obj.id, obj.name) for obj in CategoryProduct.objects.all()], label="Categoría")
class Meta:
model = Product
fields = ['name', 'category']
You can let Django's ModelForm do its work, this will create a ModelChoiceField [Django-doc], which is where the system gets stuck: it tries to assign the primary key to category, but that should be a ProductCategory object, so you can let Django handle this with:
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'category']
If you want to specify a different label, you can use the verbose_name=… [Django-doc] from the model field, or specify this in the labels options [Django-doc] of the Meta of the ProductForm. So you can specify Categoria with:
class Product(models.Model):
name = models.CharField('Nombre', max_length=150)
category = models.ForeignKey(
CategoryProduct,
on_delete=models.SET_NULL,
null=True,
related_name='products',
verbose_name='Categoria'
)
def __str__(self):
return self.name
then the CreateView can just use its boilerplate logic:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the Category model to the Product
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the category relation to products.

fields in class Meta got invalid

models.py
class Product(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
price = models.DecimalField(decimal_places=5,max_digits= 1500)
summary = models.TextField()
featured = models.BooleanField()
def __str__(self):
return self.title
# return f'product title:{self.title}-product price:{self.price}'workok
class Meta:
ordering = ('-price',)
class Opinion(models.Model):
name = models.CharField(max_length=20)
email = models.EmailField(max_length=20)
body = models.TextField()
opinion_date = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=False)
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='opinion_set')
def __str__(self):
return f'({self.name}) add opinion about ({self.product})'
forms.py:
from django.forms import ModelForm
from .models import Product #space after from keyword
class OpinionModelForm(ModelForm):
class Meta:
model = Product
fields = ['name','email','body','product']
invalid in code line :
fields = ['name','email','body','product'] #---- NOT WORK !!!
, but if i change above code to :
fields = "__all__" # ----it is WORKing ok without any problem !!
question : what is the error? I am not need all the fields in the Product model (like active boolean field), I need only 'name','email','body','product' fields .
According to the error and the code you provided the main problem is that you made a mistake in chosing model in serializer:
class OpinionModelForm(ModelForm):
class Meta:
model = Product
fields = ['name','email','body','product']
Serializer name is OpinionModelForm and listed fields belong to Opinion so I guess you actually wanted to serialize Opinion and no Product as you defined at this line:
model = Product
Simply change it to:
model = Opinion

Django formview returns object already exists error with a modelform

I'm trying to create a FormView that receives a string but it gives me a "objects already exists" error when I complete the field I give. What I'm trying to do is to create a view that checks if a certain "product" (model) exists, if that product really exists, redirect to another view based on the product "pk" to create another model.
Basically the course of action is like this:
Check if product exists.
if exists redirect to create order (model) view, else no nothing.
Fill the create order form, if valid, create the order and assign the product fk relation to order.
Here's my code
views.py
class BuyOrderCheckProduct(generic.FormView):
template_name = 'buy_order/buy_order_check_product.html'
form_class = forms.CheckProductForm
def form_valid(self, form):
try:
product = Product.objects.get(codename=form.cleaned_data['codename'])
except Product.DoesNotExist:
product = None
if product:
# Never enters here because correct existing codename gives form_invalid, don't know why
return super(BuyOrderCheckProduct, self).form_valid()
else:
# It only enters when I input a non-existent codename for product
return super(BuyOrderCheckProduct, self).form_invalid()
def form_invalid(self, form):
# I don't know why it enters here!
return super(BuyOrderCheckProduct, self).form_invalid()
def get_success_url(self, **kwargs):
# TODO: How to pass product pk as kwargs?
return reverse_lazy('order_create', self.kwargs['pk'])
class BuyOrderCreate(generic.CreateView):
template_name = 'buy_order/buy_order_create.html'
form_class = forms.BuyOrderCreateForm
success_url = reverse_lazy('buy_order_list')
# TODO: Need to create a custom form_valid to add product fk to order.
forms.py
class CheckProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['codename']
class BuyOrderCreateForm(forms.ModelForm):
class Meta:
model = BuyOrder
models.py
"""
ORDER
"""
class Order(models.Model):
class Meta:
verbose_name = u'orden'
verbose_name_plural = u'ordenes'
abstract = True
unit_price = models.IntegerField(u"precio unitario", )
quantity = models.IntegerField(u"cantidad", default=1)
discount = models.IntegerField(u"descuento")
def __unicode__(self):
return self.code
class BuyOrder(Order):
class Meta:
verbose_name = u'orden de compra'
verbose_name_plural = u'ordenes de compra'
product = models.ForeignKey(Product, related_name="buy_orders", editable = False)
bill = models.ForeignKey(BuyBill, related_name="orders", null=True, editable = False)
"""
PRODUCT
"""
class Product(models.Model):
class Meta:
verbose_name = u'producto'
verbose_name_plural = u'productos'
category = models.ForeignKey(Category, verbose_name=u'categoría', related_name='products')
codename = models.CharField(u"código", max_length=100, unique=True)
name = models.CharField(u"nombre", max_length=100)
description = models.TextField(u"descripción", max_length=140, blank=True)
sale_price = models.IntegerField(u"precio de venta", default=0)
purchase_price = models.IntegerField(u"precio de compra", default=0)
profit = models.IntegerField(u"lucro", default=0)
profit_margin = models.IntegerField(u"margen de lucro", default=0)
tax = models.IntegerField(u"tasa", default=0)
quantity = models.IntegerField(u"cantidad", default=0)
picture = models.ImageField(u"imagen", upload_to='product_pictures', blank=True)
group = models.ForeignKey(Group, verbose_name=u'grupo', related_name='products')
def __unicode__(self):
return self.name
I'll be appreciated if you give me a tip for creating a correct get_success_url() for this case.
Ok. I found a solution for my error. What caused the model already exists error was my ModelForm CheckProductForm. Codename attribute is unique, so my validation always returned False. What I did was to change my orginal ModelForm to a Form. This solved my whole issue. And for the form_invalid in form_valid issue. I've overwritten my form's clean_codename function to raise ValidationError if product doesn´t exist.
Here's the solution I found:
views.py
class BuyOrderCheckProduct(generic.FormView):
template_name = 'buy_order/buy_order_check_product.html'
form_class = forms.CheckProductForm
def form_valid(self, form):
product = Product.objects.get(codename=form.cleaned_data['codename'])
return redirect('buy_order_create', pk=product.pk)
forms.py
class CheckProductForm(forms.Form):
codename = forms.CharField(label=u'código')
def clean_codename(self):
try:
product = Product.objects.get(codename=self.cleaned_data['codename'])
except Product.DoesNotExist:
raise forms.ValidationError("This codename doesn't exist.")
return product
PD: Sorry for the dumb questions.

Limit values in the modelformset field

I've been trying to solve this problem for a couple of days now, getting quite desperate. See the commented out code snippets for some of the things I've tried but didn't work.
Problem: How can I limit the values in the category field of the IngredientForm to only those belonging to the currently logged in user?
views.py
#login_required
def apphome(request):
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
# Attempt #1 (not working; error: 'IngredientFormFormSet' object has no attribute 'fields')
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.none())
# ingrformset.fields['category'].queryset = Category.objects.filter(user=request.user)
# Attempt #2 (doesn't work)
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.filter(category__user_id = request.user.id))
models.py:
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
class Ingredient(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(Category, null=True, blank=True)
counter = models.IntegerField(default=0)
forms.py:
class IngredientForm(ModelForm):
class Meta:
model = Ingredient
fields = ('name', 'category')
UPDATE: I've made some progress but the solution is currently hard-coded and not really usable:
I found out I can control the categoryform field via form class and then pass the form in the view like this:
#forms.py
class IngredientForm(ModelForm):
category = forms.ModelChoiceField(queryset = Category.objects.filter(user_id = 1))
class Meta:
model = Ingredient
fields = ('name', 'category')
#views.py
IngrFormSet = modelformset_factory(Ingredient, form = IngredientForm, extra=1, fields=('name', 'category'))
The above produces the result I need but obviously the user is hardcoded. I need it to be dynamic (i.e. current user). I tried some solutions for accessing the request.user in forms.py but those didn't work.
Any ideas how to move forward?
You don't need any kind of custom forms. You can change the queryset of category field as:
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
IngrFormSet.form.base_fields['category'].queryset = Category.objects.filter(user__id=request.user.id)
Category.objects.filter(user=request.user)
returns a list object for the initial value in your form which makes little sense.
Try instead
Category.objects.get(user=request.user)
or
Category.objects.filter(user=request.user)[0]

django model forms filter queryset

I have the following model:
class Article(models.Model):
title = models.CharField()
description = models.TextField()
author = models.ForeignKey(User)
class Rating(models.Model):
value = models.IntegerField(choices=RATING_CHOICES)
additional_note = models.TextField(null=True, blank=True)
from_user = models.ForeignKey(User, related_name='from_user')
to_user = models.ForeignKey(User, related_name='to_user')
rated_article = models.ForeignKey(Article, null=True, blank=True)
dtobject = models.DateTimeField(auto_now_add=True)
Based upon the above model, i have created a model form, as follows:
Model Forms:
class RatingForm(ModelForm):
class Meta:
model = Rating
exclude = ('from_user', 'dtobject')
Excluding from_user because the request.user is the from_user.
The form renders well, but in to_user in the dropdown field, the author can rate himself as well. So i would want the current_user's name to populate in the dropdown field. How do i do it?
Override __init__ to remove current user from the to_user choices.
Update: More Explanation
ForeignKey uses ModelChoiceField whose choices are queryset. So in __init__ you have to remove the current user from to_user's queryset.
Update 2: Example
class RatingForm(ModelForm):
def __init__(self, current_user, *args, **kwargs):
super(RatingForm, self).__init__(*args, **kwargs)
self.fields['to_user'].queryset = self.fields['to_user'].queryset.exclude(id=current_user.id)
class Meta:
model = Rating
exclude = ('from_user', 'dtobject')
Now in the view where you create RatingForm object pass request.user as keyword argument current_user like this.
form = RatingForm(current_user=request.user)