Django Clean_Field validation using field value from different Model - django

I am trying to validate a form field in a ModelForm based on the value of another field in a different model.
Here is my ModelForm. CompanyData Model contains an "arr" field that I want to use as the value to validate against (that the "rev_goal" is higher):
from django.forms import ModelForm
from django import forms
from django.core.exceptions import ValidationError
from company_data.models import CompanyData
from plans.models import Plan
class PlanCreateForm(forms.ModelForm):
def clean_rev_goal(self):
rev_goal = self.cleaned_data['rev_goal']
company_data = self.object.companydata = CompanyData.objects.get # This is wrong. But how do I get the arr field from CompanyData here so the next line of code works?**
if rev_goal < company_data.arr:
raise ValidationError("Revenue goal must be greater than current ARR $")
return rev_goal
class Meta:
model = Plan
fields = ['plan_name', 'rev_goal', 'months_to_project', 'cpl', 'conv_rate']
More detail was requested so here are the two models showing the relationships between the two:
class CompanyData(models.Model):
user = models.OneToOneField(User)
arr = models.DecimalField(max_digits=20, decimal_places=2, validators=[MinValueValidator(1)])
num_cust = models.IntegerField(validators=[MinValueValidator(1)])
class Plan(models.Model):
companydata = models.ForeignKey(CompanyData, related_name='companydata',
on_delete=models.CASCADE)
user = models.ForeignKey(User)
plan_name = models.CharField(max_length=255)
rev_goal = models.DecimalField(max_digits=20, decimal_places=2, validators=[validate_rev_goal])
months_to_project = models.DecimalField(max_digits=20, decimal_places=0, validators=[MinValueValidator(1)])
cpl = models.DecimalField(max_digits=20, decimal_places=2, validators=[MinValueValidator(.01)])
conv_rate = models.DecimalField(max_digits=5, decimal_places=2, validators=[MinValueValidator(.01)])
And I going about this completely wrong? Or is it possible to access the value of a field of a different model in a ModelForm? Thanks in advance for your help!

Related

Django model select between two columns/fields?

I have a Slider module that i want to include items from movies_movie and shows_show table. An item can either be a show or movie. How do i make user select between movie and show? Currently i have columns for movie and show but how do i force user to select between the two?
also title_en is a column in movie or tv show tables. So the title of the movie/show selected should display in row after save.
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True)
def __str__(self):
return self.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
Also if a show is selected and a movie isn't, how do i know title_en will be taken from show and not movie?
I think you can do something like this:
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
# don't forget to add null=True to both fields
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True, null=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True, null=True)
# see docs, https://docs.djangoproject.com/en/3.2/ref/models/instances/#django.db.models.Model.clean
def clean(self):
if self.movie and self.show:
raise ValidationError({'movie': _('You can't select both types at the same time')})
elif not self.movie and not self.show:
raise ValidationError({'movie': _('You must select one type')})
def __str__(self):
return self.movie.title_en if self.movie else self.show.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
You may consider using django contenttypes.
Imagine in the future, you have not just Movie, Show, but have new Class such as Book, Podcase, it might not be a good idea to keep adding new foreignkey to your Slider Model.
I have not used contenttype before, so I am referencing this SO answer.
(using python 3.6, django 3.2)
models.py
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Movie(models.Model):
title = models.CharField(max_length=50)
director = models.CharField(max_length=50)
class Show(models.Model):
title = models.CharField(max_length=50)
date = models.DateField()
class Slider(models.Model):
order = models.IntegerField(max_length=3, blank=True)
choices = models.Q(model='movie') | models.Q(model='show')
selection_type = models.ForeignKey(
ContentType, limit_choices_to=choices,
on_delete=models.CASCADE)
selection_id = models.PositiveIntegerField()
selection = GenericForeignKey('selection_type', 'selection_id')
def __str__(self):
return self.selection.title
admin.py
#admin.register(Slider)
class SliderAdmin(admin.ModelAdmin):
pass
at django shell, the following is valid.
movie = Movie.objects.create(title='movie 1', director='ben')
show = Show.objects.create(title='show 1', date='2021-01-01')
s1 = Slider.objects.create(selection=movie, order=1)
s2 = Slider.objects.create(selection=show, order=2)
However, using limit_choices_to only restrict the choices in admin page, and there is no constraint at database level. i.e. the following are actually legal.
place = Place.objects.create(name='home')
s3 = Slider.objects.create(selection=s3, order=3)
I have not found a fix for this issue yet. Maybe doing some validation in save method is a way (see the comments under this).

How to add an extra field for an existing model in case of many to many relationships in django

So I have a product Model which say looks like this :
class ProductModel(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=6, decimal_places=2, null=True)
and I also have a cart model which looks like this :
class CartModel(models.Model):
customer = models.ForeignKey(User, on_delete= models.CASCADE, related_name="cart")
products = models.ManyToManyField(ProductModel)
the thing is I want to add a quantity field to the product so the user can add multiple products from the same product in the cart But I only want it when the product is in a cart (I don't want to add it in the ProductModel) Instead I want to add it to the product fields in the many to many relationship. I've done a bit of research and most of the answers aren't clear enough on how I should be doing this.
You can create new fields in the intermediate relation table between Products and Cart. You have to define a new class for this intermediate table and to use it with the through attribute of the M2M field.
from django.db import models
from django.contrib.auth.models import User
class ProductModel(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=6, decimal_places=2, null=True)
def __str__(self):
return self.name
class Meta:
verbose_name = "Product"
class CartModel(models.Model):
customer = models.ForeignKey(User, on_delete=models.CASCADE, related_name="cart")
products = models.ManyToManyField(ProductModel, through='ProductCart')
def __str__(self):
return "Cart nÂș {} - {}".format(self.pk, self.customer)
class Meta:
verbose_name = "Cart"
class ProductCart(models.Model):
product = models.ForeignKey(ProductModel, on_delete=models.CASCADE)
cart = models.ForeignKey(CartModel, on_delete=models.CASCADE)
quantity = models.IntegerField()
def __str__(self):
return ' '
One way for displaying it in the admin can be to use TabularInline for the products of the cart:
from django.contrib import admin
from .models import CartModel
class ProductsInline(admin.TabularInline):
model = CartModel.products.through
extra = 0
exclude = ()
verbose_name = 'Product'
class CartAdmin(admin.ModelAdmin):
list_display = ('customer',)
list_filter = ('customer',)
inlines = [
ProductsInline,
]
admin.site.register(CartModel, CartAdmin)

how to make a form field exact?

In django how to make form field exact, i.e it will have choices?
My forms.py:
from django import forms
class FilterForm(forms.Form):
category = forms.CharField()
price = forms.IntegerField()
My models.py:
CATEGORY_CHOICES = (
('Fruits and Vegetables', 'Fruits and Vegetables'),
('Electronics', 'Electronics'),
('Clothing', 'Clothing'),
('Books', 'Books'),
)
class Item(models.Model):
title = models.CharField(max_length=120)
price = models.FloatField()
discount_price = models.FloatField(blank=True, null=True)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=120, null=True, blank=True)
image_url = models.CharField(max_length=2083, null=True, blank=True)
slug = models.SlugField(null=True, blank=True)
description = models.TextField(null=True, blank=True)
Please make use of a ModelForm [Django-doc]. A ModelForm is capable of automating a lot of aspects when creating or update model records. Furthermore it can automatically construct the fields based on the fields of the model. You can, if you want to, alter the widgets, etc. But usually a ModeLField is a good starting point.
Here you thus can construct a form like:
# app/forms.py
from django import forms
from app.models import Item
class FilterForm(forms.ModelForm):
class Meta:
model = Item
fields = ['category', 'price']
Where you replace app with the name of the app.
You can use ModelForm
from django import forms
from .models import Item
class FilterForm(forms.ModelForm):
class Meta:
model = Item
fields = [
'category',
'price'
]
If you wanna stick with Form, use Choice Field and copy the Choices in form
from django import forms
class FilterForm(forms.Form):
CATEGORY_CHOICES = (
('Fruits and Vegetables', 'Fruits and Vegetables'),
('Electronics', 'Electronics'),
('Clothing', 'Clothing'),
('Books', 'Books'),
)
category = forms.ChoiceField(choices=CATEGORY_CHOICES)
price = forms.IntegerField()

How can user registration form for base django user model create ForeignKey user

I am new to django and am trying to set up a simple employee timesheet site that has multiple users. I set up two models one for the individual employee that has a ForeignKey of the base django user and a timesheet model that has a ForeignKey of the employee model. I'm not sure this is correct because when I use my registration form it just creates the base django user and not the "Employee" so when I want to create a new timesheet entry only the one employee is set up (set up with admin page). Can someone with more django experience tell me if there is a better way to do this (different model relationship, etc)
from django.urls import reverse
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils import timezone
import datetime
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='employee')
payRate = models.DecimalField(max_digits=4, decimal_places=2, default=15.00, verbose_name=("Pay"))
vacTotal = models.DecimalField(max_digits=5, decimal_places=2, default=200.00, verbose_name=("Vacation"))
# META CLASS
class Meta:
verbose_name = 'employee'
verbose_name_plural = 'employees'
# TO STRING METHOD
def __str__(self):
return f"{self.user}"
class Tsheet(models.Model):
# CHOICES
WORK_CHOICES= (
('W', 'Regular Work'),
('V', 'Vacation'),
('S', 'Sick',),
('C','Call In'),
)
# DATABASE FIELDS
name = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='name')
workType = models.CharField(max_length=15,choices=WORK_CHOICES)
workDate = models.DateField(verbose_name=("Date"), default=datetime.date.today, editable=True)
workDescription = models.CharField(max_length=200)
workHours = models.DecimalField(max_digits=4, decimal_places=2, default=8.00, verbose_name=("Hours"))
workReviewed= models.BooleanField(default=False)
slug = models.SlugField(max_length=50, unique=True,
help_text='Unique value for timesheet entry URL, created automatically from name.')
# META CLASS
class Meta:
verbose_name = 'tsheet'
verbose_name_plural = 'tsheets'
# TO STRING METHOD
def __str__(self):
return f"{self.name} - {self.workDate} - {self.workHours} - {self.workType}"
# SAVE METHOD
# ABSOLUTE URL METHOD
def get_absolute_url(self):
return reverse('entry-detail', kwargs={'pk': self.pk})```
The right way to approach this is to extend the AbstractUser and add the fields there:
class User(AbstractUser):
payRate = models.DecimalField(max_digits=4, decimal_places=2, default=15.00, verbose_name=("Pay"))
vacTotal = models.DecimalField(max_digits=5, decimal_places=2, default=200.00, verbose_name=("Vacation"))
Then you have a single table with all the data from the default Django User as well as your specific fields

Django: model does not render fields correctly in Admin

I've a model that I know is recording correctly the values in DataBase, but it is not showing them correctly in Admin Panel.
I know it's saving this fields correctly because:
1.- I can Query the model in Shell and see the values correctly.
2.- I'm using this model's fields to create another model, and this other model saves correctly and SHOWs correctly it's fields in Admin Panel.
What could be wrong?
Shell:
>>> SizeQuantity.objects.get(pk=9)
<SizeQuantity: variante_125 por cantidad_200>
>>> SizeQuantity.objects.get(pk=9).size
'variante_125'
>>> SizeQuantity.objects.get(pk=9).quantity
'cantidad_200'
What I see in AdminPanel:
This is my other model that uses values from SizeQuantiy:
I was expecting to render the Size and Quantity fields like this for my SizeQuantity model:
from .models import Cart, SizeQuantity
# Register your models here.
admin.site.register(Cart)
admin.site.register(SizeQuantity)
models.py:
class SizeQuantity(models.Model):
cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
size = models.CharField(max_length=20, choices=TAMANIOS)
quantity = models.CharField(max_length=20, choices=CANTIDADES)
image = models.ImageField(upload_to='images', blank=True, null=True)
comment = models.CharField(max_length=200, blank=True, null=True, default='')
uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.id) + " - " + str(self.size) + " por " + str(self.quantity)
#property
def image_filename(self):
return self.image.url.split('/')[-1]
Probably Django can't render your fields correctly because mentioned values are not included in fields choices (TAMANIOS CANTIDADES)
Your field above ('variante_125', 'cantidad_200') is not in your valid CHOICES.
You can add any char for size and quantity fields although you don't write the values in CHOICES. But you can't use the choices such as get_FOO_display. It's helpful to check docs here (https://docs.djangoproject.com/en/2.1/ref/models/fields/#choices).
You should check TAMANIOS, CANTIDADES choices that have those values.