My Django model has a pricing_plan choice field. There are 12 fields whose value depends on the pricing_plan value.
class Organisation(models.Model):
PRICING_CHOICES = (
('basic', 'Basic'),
('team', 'Team'),
('super', 'Super')
)
# some fields...
pricing_plan = models.CharField(max_length=50, default='basic')
max_host_accounts = models.IntegerField(default=1)
max_admin_accounts = models.IntegerField(default=1)
max_guests = models.IntegerField(default=10)
# more fields whose value depends on pricing_plan value
For every different pricing_plan these fields get specific values. In code that could be described like:
if pricing_plan == 'basic':
max_host_accounts = 1
max_admin_accounts = 1
max_guests = 10
...
elif pricing_plan == 'team':
max_host_accounts = 10
max_admin_accounts = 3
max_guests = 25
...
However, there might be more pricing plans in the future and more options and I am afraid that an if/elif/else statement will be huge and not-easily readable.
What would be the best/idiomatic way to implement that in a Django model?
Use more CHOICE tuples with constant values for every pricing plan?
Use Enum classes with constant values for every pricing plan?
Use Organisation as parent class and create subclasses
like:
.
class BasicOrganisation(Organisation):
max_host_accounts = models.IntegerField(default=1)
max_admin_accounts = models.IntegerField(default=1)
max_guests = models.IntegerField(default=10)
class TeamOrganisation(Organisation):
max_host_accounts = models.IntegerField(default=10)
max_admin_accounts = models.IntegerField(default=3)
max_guests = models.IntegerField(default=25)
Anything else?
I would do it like that (I've used django-choices package for the pseudo-Enum emulation):
from django.db import models
from djchoices import ChoiceItem, DjangoChoices
def get_max_admin_accounts(pricing_plan):
if pricing_plan == Organization.Pricing.BASIC:
return 1
# other code
class Organization(models.Model):
class Pricing(DjangoChoices):
BASIC = ChoiceItem('basic', 'Basic')
TEAM = ChoiceItem('team', 'Team')
SUPER = ChoiceItem('super', 'Super')
pricing_plan = models.CharField(max_length=50, default=Pricing.BASIC)
max_host_accounts = models.IntegerField()
max_admin_accounts = models.IntegerField()
max_guests = models.IntegerField()
def save(self, **kwargs):
if not self.max_admin_accounts:
self.max_admin_accounts = get_max_admin_accounts(self.pricing_plan)
# other fields filled
super().save(**kwargs)
I would do like
class Organisation(models.Model):
PRICING_CHOICES = {
"basic": ("Basic", (1, 1, 10)),
"team": ("Team", (10, 3, 25)),
}
# some fields...
pricing_plan = models.CharField(choices=tuple([(i,j[0]) for i, j in PRICING_CHOICES.items()]), max_length=50, default='basic')
#other fields
def set_plan(self, max_host_accounts, max_admin_accounts, max_guests):
self.max_host_accounts = max_host_accounts
self.max_admin_accounts = max_admin_accounts
self.max_guests = max_guests
def save(self, *args, **kwargs):
if getattr(self, "plan_changed", ""): #you need to set this attribute whenever updating a plan. like model_obj.plan_changed = True
#otherwise you need to check db whether plan is changed or not.
self.set_plan(*self.PRICING_CHOICES[self.pricing_plan][1])
super(Organisation, self).save(*args, **kwargs)
Related
I have product serializer which return category_offer_price & product_offer_price,
before getting this response I want to compare both price and only return whichever is highest price.
#Serilaizer.py
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
product_offer_price = SerializerMethodField()
category_offer_price = SerializerMethodField()
class Meta:
model = Products
fields = [
"id",
"product_name",
"slug",
"category",
"description",
"category_offer_price",
"product_offer_price",
"base_price",
"stock",
"is_available",
"created_date",
"images",
"images_two",
"images_three",
]
def get_product_offer_price(self, obj):
try:
product_offer = ProductOffer.objects.get(product=obj)
if product_offer.is_active:
offer_price = product_offer.product_offer_price()
return offer_price
except Exception:
pass
return None
def get_category_offer_price(self, obj):
try:
category_offer = CategoryOffer.objects.get(category=obj.category)
if category_offer.is_active:
offer_price = category_offer.category_offer_price(obj)
return offer_price
except Exception:
pass
return None
#Models.py
class Products(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
product_name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=100, unique=True)
description = models.TextField(max_length=500)
base_price = models.IntegerField()
images = models.ImageField(upload_to="photos/products")
images_two = models.ImageField(upload_to="photos/products")
images_three = models.ImageField(upload_to="photos/products")
stock = models.IntegerField()
is_available = models.BooleanField(default=True)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Products"
def __str__(self):
return self.product_name
I'd like to know is it possible to compare serializer fields in a serializer class?
You can move into one method, to validate your field. Also, substitute your try:except with get-object-or-404 method and your serializer fields with all value since you are using everything, to have a much cleaner code.
from django.shortcuts import get_object_or_404
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
price = SerializerMethodField()
class Meta:
model = Products
fields = '__all__'
def get_price(self, obj):
product_offer = get_object_or_404(ProductOffer, product=obj)
category_offer = get_object_or_404(CategoryOffer, category=obj.category)
if product_offer.is_active and category_offer.is_active:
if product_offer.product_offer_price() > category_offer.category_offer_price(obj):
return product_offer.product_offer_price()
else:
return category_offer.category_offer_price(obj)
elif product_offer.is_active and not category_offer.is_active:
return product_offer.product_offer_price()
elif category_offer.is_active and not product_offer.is_active:
return category_offer.category_offer_price(obj)
EDIT: As you can see I used the classic if/else in this solution, although since Python 3.10 you can use the Match case statement to substitute these conditions chain.
In case of objects do not exist:
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
price = SerializerMethodField()
class Meta:
model = Products
fields = '__all__'
def get_price(self, obj):
try:
product_offer = ProductOffer.objects.filter(product=obj).first()
category_offer = CategoryOffer.objects.filter(category=obj.category).first()
if not product_offer and not category_offer:
return obj.base_price
elif not category_offer:
return product_offer.product_offer_price()
elif not product_offer:
return category_offer.category_offer_price(obj)
elif category_offer and product_offer:
if category_offer.is_active and not product_offer.is_active:
return category_offer.category_offer_price(obj)
elif product_offer.is_active and not category_offer.is_active:
return product_offer.product_offer_price()
elif category_offer.is_active and product_offer.is_active:
if category_offer.category_offer_price(obj) > product_offer.product_offer_price():
return category_offer.category_offer_price(obj)
else:
return product_offer.product_offer_price()
except:
return obj.base_price
Although, to be honest, if there could be no objects then the is_active field is redundant.
You can override to_representation()
Example:
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
product_offer_price = SerializerMethodField()
category_offer_price = SerializerMethodField()
...
...
def to_representation(self, instance):
data = super().to_representation(instance)
# access required fields like this
product_offer_price = data['product_offer_price']
category_offer_price = data['category_offer_price']
# do calculations here and returning the desired field as `calculated_price`
if category_offer_price > product_offer_price:
data['calculated_price'] = category_offer_price
else:
data['calculated_price'] = product_offer_price
return data
Not sure it s what you want but you could use a field of type SerializerMethodField which allow you to add a computed field that you could call category_offer_higher_price. Its value is computed by a function that return the highest one. See following link : https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
I have made a Django model form but the problem is in my logic I am using something else and now I want to figure out a way to validate it by either defining a Meta class and choosing the fields that I want to display to the user but of course this won't validate the form.
Now I want to know if there is a way to validate the form without touching the models and pass the data required for the logic and after take care of the information needed for the data of the model to be saved.
Here is the models:
from django.db import models
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class RoomCategory(models.Model):
name = models.CharField(max_length=59)
price = models.IntegerField()
beds = models.PositiveIntegerField()
capacity = models.PositiveIntegerField()
size = models.CharField(max_length=59)
def __str__(self):
return self.name
class Room(models.Model):
room_number = models.CharField(max_length=60)
room_category = models.ForeignKey(RoomCategory, on_delete=models.CASCADE)
def __str__(self):
return f"The room {self.room_number} {self.room_category} has a maximum of {self.room_category.capacity} person and cost {self.room_category.price}/night "
class Booking(models.Model):
customer = models.ForeignKey(User, on_delete=models.CASCADE)
room = models.ForeignKey(RoomCategory, on_delete=models.CASCADE)
check_in = models.DateField()
check_out = models.DateField()
adults = models.PositiveSmallIntegerField()
children = models.PositiveSmallIntegerField()
def __str__(self):
return f"{self.customer} has booked for {self.room} from {self.check_in} to {self.check_out}"
Here is the form:
class BookingForm(forms.ModelForm):
class Meta:
model = Booking
fields = ['room', 'check_in', 'check_out', 'adults', 'children']
here is the views.py
data = form.cleaned_data
roomlist = Room.objects.filter(room_category__name=data['room'])
available_rooms = []
for room in roomlist:
if data['adults'] + data['children'] > room.room_category.capacity:
return HttpResponse(f'Sorry !! But this category of room cannot handle more than {room.room_category.capacity}')
else:
if check_availability(room.room_category.name, data['check_in'], data['check_out'], data['adults'], data['children']):
available_rooms.append(room)
if len(available_rooms) > 0:
room = available_rooms[0]
new_booking = Booking.objects.create(
customer=self.request.user,
room=room,
check_in=data['check_in'],
check_out=data['check_out'],
adults=data['adults'],
children=data['children']
)
new_booking.save()
return HttpResponse(new_booking)
else:
return HttpResponse('All the rooms of this type are not available')
It is not printing the data means that the form is not valid and it fall down to the else statement.
You can validate any field in the form by writing a method in this way : def clean_(field_name) i.e def clean_room(self) read more:
https://docs.djangoproject.com/en/3.1/ref/forms/validation/#cleaning-a-specific-field-attribute
I have a table that has name and amount entity, and I want to happen is:
this is the sample of my table on models.py:
class Test(models.Model):
test_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=50)
amount = models.IntegerField()
timestamp = models.DateTimeField()
if entered amount is 300, the name will be 'hello', else if the entered amount is 500, the name will be 'world'. please help, been trying to figure this out since yesterday, I'm really newbie on python and django.
Create a custom save() method sth like that :
class Test(models.Model):
test_id = models.IntegerField(primary_key=True)
amount = models.IntegerField()
name = models.CharField(max_length=50)
timestamp = models.DateTimeField()
def save(self, *args, **kwargs):
self.name = 'hello' if self.amount == 300 else 'world'
super(Test, self).save(*args, **kwargs)
Either you can handle this on model's custom save method or on serializer level (ModelSerializer) if you're using rest apis. The recommended way is to handle these kind of data manipulation in your model's serializer. Use a custom create/update method in your serializer (The logic on both solutions remains the same):
class TestSerializer(serializers.ModelSerializer):
amount = serilaizers.IntegerField()
name = serilaizers.CharField()
class Meta:
model = Test
fields = [
'test_id',
'amount',
'name',
'timestamp',
]
def create(self, validated_data):
if validated_data['amount'] > 1200:
validated_data['name'] = 'dry'
elif validated_data['amount'] == 0:
validated_data['name'] = 'wet'
elif 900 <= validated_data['amount'] <= 1200:
validated_data['name'] = 'light'
elif 500 <= validated_data['amount'] < 900:
validated_data['name'] = 'medium'
elif 0 < validated_data['amount'] < 500:
validated_data['name'] = 'heavy'
return Test.objects.create(**validated_data)
In a Django Modelform (Product_definition), i want to have a dropdown(Merchant name) which will show users only if the their designation in User form is "Merchant".
is it possible that I could get the list of users for the dropdown based on this condition .Please note that i don't require it to be a foreign key as connecting the models is not required.
This is the form which contains the Designation :
from django.contrib.auth.models import User
class UserProfileInfo(models.Model):
user = models.OneToOneField(User,on_delete = models.CASCADE)
#extra UserAttribute
MERCHANT = 'MR'
FABRIC = 'FR'
WASHING = 'WS'
PRINT = 'PR'
PLANNER = 'PL'
DESIGNATION_CHOICES =(
(PLANNER,'Planner'),
(MERCHANT,'Merchant'),
(FABRIC,'Fabric'),
(WASHING,'Washing'),
(PRINT,'Printing'),
)
Designation =models.CharField(
max_length = 20,
choices = DESIGNATION_CHOICES,
default= 'PLANNER'
)
def __str__(self):
return self.user.username
and this is the form with Merchant Name where I want the names of all merchants to appear.
class Product_definition(models.Model):
Order_number = models.CharField(max_length=25,unique = True, blank = True, null = True)
style_name = models.CharField(max_length=15, blank = True, null = True)
color = models.CharField(max_length=15, blank = True, null = True)
Order_qty = models.PositiveIntegerField()
SMV = models.FloatField()
MERCHANT = models.ForeignKey(UserProfileInfo,on_delete= models.CASCADE,default='Select')
def __str__(self):
return self.Order_number
I have created a foreign key for now but I don't require it and it doesn't list the names of only the merchant in the drop down.
I think you can do it like this using ModelChoiceField:
class ProductForm(forms.ModelForm): # please use CamelCase when defining Class Names
MERCHANT = forms.ModelChoiceField(queryset=UserProfileInfo.objects.filter(Designation=UserProfileInfo.MARCHENT)) # Please use sname_case when naming attributes
class Meta:
model = Product_definition # Please use CamelCase when defining model class name
fields = '__all__'
This is my first model
from django.db import models
from django.contrib.auth.models import User
CHOICES = (('Earned Leave','Earned Leave'),('Casual Leave','Casual Leave'),('Sick Leave','Sick Leave'),('Paid Leave','Paid Leave'))
STATUS_CHOICES = (('0', 'Rejected'),('1', 'Accepted'),)
class Leave(models.Model):
employee_ID = models.CharField(max_length = 20)
name = models.CharField(max_length = 50)
user = models.ForeignKey(User, on_delete = models.CASCADE, null =True)
type_of_leave = models.CharField(max_length = 15, choices = CHOICES)
from_date = models.DateField()
to_date = models.DateField()
status = models.CharField(max_length = 15, choices = STATUS_CHOICES)
#property
def date_diff(self):
return (self.to_date - self.from_date).days
This is my second model
class History(models.Model):
first_name = models.CharField(max_length = 50)
last_name = models.CharField(max_length = 50)
employee_ID = models.CharField(max_length = 20)
earned_leave = models.IntegerField()
casual_leave = models.IntegerField()
sick_leave = models.IntegerField()
paid_leave =models.IntegerField()
Here upon saving the first model Leave, I have written to override save method like,
def save(self, *args, **kwargs):
super(Leave, self).save()
if self.employee_ID == History.employee_ID:
if self.status == '1':
if self.type_of_leave == 'Earned Leave':
history = History.objects.update(
earned_leave = self.date_diff,
)
But upon saving the first model, all the entries in the History model are getting updated. Where in the history table every user have a separate entry with user's details(first_name, last_name, employee_ID) and default values as 10 for the rest. Upon saving the Leave model only the entry that is associated with the employee_ID of Leave model should be updated in the History model. For that purpose i have given as if self.employee_ID == History.employee_ID: but it isn't working.
I've even tried as -
def save(self, *args, **kwargs):
super(Leave, self).save()
if self.employee_ID == History.employee_ID:
if self.status == '1':
if self.type_of_leave == 'Earned Leave':
history = History.objects.update(
earned_leave = History.earned_leave - self.date_diff,
)
But this is not working, nothing gets updated and get'a an error unsupported operand types
So, the base of the project is employee-leave-management. As the user applies for the leave and is accepted the number of days left should get updated in History table or model.
If there's any alternate method, share.
History.objects.update(...) does in fact update all the History objects. You should specify which ones you want to update in your query:
from django.db.models import F
def save(self, *args, **kwargs):
super(Leave, self).save()
if self.status == '1':
if self.type_of_leave == 'Earned Leave':
history = History.objects.filter(employee_id=self.employee_id).update(
earned_leave = F('earned_leave') - self.date_diff,
)
The F() expression here refers to the value of the column and will be computed by the database rather than in Python.