Validate an inline foreign key field - django

I'm having problems trying to validate a form so that it only ever picks a distinct partner. The classes work like this:
class EmpployeeAdmin(admin.ModelAdmin):
#.......
class EmployeeRoles(models.Model):
partner = model.ForeignKey(Partner, relative_name='employee')
employee = model.ForeignKey(Employee, relative_name='partner')
class EmployeeRolesInline(admin.TabularInline):
model = EmployeeRoles
extra = 0
form = EmployeeRolesForm
Inside my forms.py I use the clean_partner function to try and validate that the current Employee only has one partner of the same name. They can have multiple EmployeeRoles objects with different partners, but they should only be assigned a partner once.
class EmployeeRolesForm(forms.ModelForm):
def clean_partner(self):
partner = self.cleaned_data.get('partner')
partner_ids=[int(p.partner_id) for p in self.instance.employee.partners.all()]
if self.instance.partner_id is not None:
return
else:
if partner.id in partner_ids:
raise forms.ValidationError("Partner already chosen")
This doesn't work, it gives me an error that self.instance.employee.partners.all() is empty. I think it has something to do with the form being unbounded and I'm not sure how to solve the problem. It's the same problem I had when I tried to filter the partner selection after every save so you only see the partners you don't have when you try to add a new employeerole. If anyone can suggest a way around the error I'd appreciate it.

You can just use unique_together in your model:
class EmployeeRoles(models.Model):
partner = model.ForeignKey(Partner, relative_name='employee')
employee = model.ForeignKey(Employee, relative_name='partner')
class Meta:
unique_together = (("partner", "employee"),)

Related

Django - Custom values for inline list item field

After searching for a while, I can't seem to find any answer for this so I'm asking this question. What I want to is display some values based on a person's salary and a bracket. My models looks like this.
class Employee
salary = models.DecimalField
class Benefit
some_fields
class EmployeeBenefit
employee = models.ForeignKey
benefit = models.ForeignKey
class BenefitVersion
benefit = models.ForeignKey
class BenefitBracket
benefit_version = models.ForeignKey
from_salary = models.DecimalField
to_salary = models.DecimalField
the_fields_I_want_to_display
As you can see it's quite deep, there would be a bunch of querying to do to get to the fields I want.
I basically need to get the employee's salary(which is easy because this is gonna be inside EmployeeAdmin) then get the current benefit of the EmployeeBenefit list item, then based on the benefit and the employee's salary, get the bracket and then display some of it's fields on the inline.
I want to display the the_fields_I_want_to_display on the admin.TabularInline for EmployeeBenefit inside my EmployeeAdmin. I was testing using a forms.ModelForm in the inline and modifying it's contents using get_form based on this answer but django is not calling get_form. I also previously tried using calculated_fields but it's not being rendered as well.
I'm using django 3.1.7 btw
Edit:
I found this and try it out right now. I think it has potential but unfortunately the obj that get_formset gives is Employee so I still need to find a way to get the right EmployeeBenefit for the list item
After some more searching, I already figured out a way to do this. Based on this SO answer, I got an idea on how I should receive the data I passed to the formset using get_formset.
So, in my get_formset, I did something like this.
def get_formset(self, request, obj=None, **kwargs):
formset = super(Inline, self).get_formset(request, obj, **kwargs)
formset.request = request
benefit_details_dict_arr = []
emp_contribs = models.EmployeeBenefit.objects.filter(employee=obj)
#because what I'm getting here is Employee and not a EmployeeBenefit, I have to query it first
for contrib in emp_contribs:
bracket = contrib.get_benefit_bracket(datetime.now())
benefit_details_dict_arr.append({
"key": bracket.val,
"key2": bracket.val2,
})
formset.benefit_details = benefit_details_dict_arr
return formset
Then on the formset, I receive the data this way.
class EmpBenefitFormSet(forms.models.BaseInlineFormSet):
model = models.EmployeeBenefit
def __init__(self, *args, **kwargs):
super(EmpBenefitFormSet, self).__init__(*args, **kwargs)
cntr = 0
for form in self.forms:
init_data = self.benefit_details[cntr]
for field in form.fields:
if field in init_data:
form.initial[field] = init_data[field]
form.fields[field].required = False
form.fields[field].disabled = True
cntr += 1
Also, if you're gonna use this way of receiving the data, each field you want to inflate should be declared on the fields of the inline. This should work if the fields you want to set an initial value are actual fields of the model. You might need to tweak this a bit to work for custom fields

How to Update models field based on another field of same models

class PurchaseOrder(models.Model):
purchase_order_id = models.AutoField(primary_key=True)
purchase_order_number = models.CharField(unique=True)
vendor = models.ForeignKey(Vendor)
i am creating Purchase Order(po) table. when po created i have to update purchase_order_number as "PO0"+purchase_order_id ex PO0123 (123 is Primary key). so i am using def save in models to accomplish this
def save(self):
if self.purchase_order_id is not None:
self.purchase_order_number = "PO"+str(self.purchase_order_id)
return super(PurchaseOrder, self).save()
It is working fine with single creation but when i try to create bulk of data using locust(Testing tool) its giving an error duplicate entry for PurchseOrdernumber Can we modify field value in models itself some thing like this
purchase_order_number = models.CharField(unique=True,default=("PO"+self.purchase_order_id )
To be honest, I don't think it should work when you create multiple instances. Because as I can see from the code:
if self.purchase_order_id is not None:
self.purchase_order_number = "PO"+str(self.purchase_order_id)
Here purchase_order_id will be None when you are creating new instance. Also, until you call super(PurchaseOrder, self).save(), it will not generate purchase_order_id, meaning purchase_order_number will be empty.
So, what I would recommend is to not store this information in DB. Its basically the same as purchase_order_id with PO in front of it. Instead you can use a property method to get the same value. Like this:
class PurchaseOrder(models.Model):
purchase_order_id = models.AutoField(primary_key=True)
# need to remove `purchase_order_number = models.CharField(unique=True)`
...
#property
def purchase_order_number(self):
return "PO{}".format(self.purchase_order_id)
So, you can also see the purchase_order_number like this:
p = PurchaseOrder.objects.first()
p.purchase_order_number
Downside of this solution is that, you can't make any query on the property field. But I don't think it would be necessary anyway, because you can do the same query for the purchase_order_id, ie PurchaseOrder.objects.filter(purchase_order_id=1).

related field nested lookup error

I have the following models:
class Profile(models.Model):
user = models.ForeignKey(User)# User can have more than one profile
class Order(models.Model):
ship_to = models.ForeignKey(Profile)
class Shipping(models.Model):
order = models.ForeignKey(Order)# one order can have more than one shipping
shipping_company = models.ForeignKey(Shipping_company)
class Shipping_company(models.Model):
name = ...
So now i have the following structure:
User > Receiver > Order > Shipping > Shipping_company
The question is: How can i get all User models, who ordered with specific Shipping company?
If i make a query like this
User.objects.filter(receiver__order__shipping__shipping_company__pk=1)
i get
FieldError: Relation fields do not support nested lookups
if i make something like this
sh_comp = items.objects.get(pk=1) # __unicode__ returns "FedEx"
User.objects.filter(receiver__order__shipping__shipping_company=sh_comp)
the result is
ValueError: Cannot query "FedEx": Must be "Receiver" instance.
This seemed to be a simple and trivial task, but i can't make it work.
One approach that can be taken is as following(I am only considering the four models you have presented in your question),
You have foreign key of Shipping company in Shipping model. So you can make use of model function here on Shipping_company model.
Take a look at this model function
class Shipping_company(models.Model):
fields...
def get_profiles(self):
shippings = Shipping.objects.filter(shipping_company=self)
users = list(set([x.order.ship_to for x in shippings]))
Explanation:
shippings = Shipping.objects.filter(shipping_company=self)
will return all the shippings for one Shipping company(FedEx in your case). Further loop through the shippings to get ship_to from order field.
PS: You can take it as reference and design your own solution.
Walkthrough:
Lets say there is shipping company 'FedEx'. So we do,
fedex = Shipping_company.objects.get(name='FedEx')
Now, when you call get_profiles on fedex, like
fedex.get_profiles()
what will happen is this.
fedex instance refers to self in get_profiles() function now.
Using self(fedex), we filter out shippings by fedex.
Then we loop through those shippings to get order per shipping and each of that order has a ship_to(profile) foreign key.
I guess, you are getting confused because of the return statement.
In elaborate fashion the whole function will look something like this
def get_profiles(self):
users = list()
shippings = Shipping.objects.filter(shipping_company=self)
for shipping in shippings:
order = shipping.order
#Now you have an order per shipping, so you do
if not order.ship_to in users:
users.append(order.ship_to)
return users

Django validate data when updating model with primary key

I am having trouble with updating fields of a model instance. The model is as follows:
class commonInfo(models.Model):
mothers_id = models.IntegerField(primary_key=True)
date = models.DateField()
data_collector = models.CharField(max_length=50)
Essentially, I just want to do this, but it won't work because commonInfo has a user defined primary key
commonInfo_form(request.POST or None).is_valid()
Since I am updating, I am overriding date and data_collector, but not mothers_id. So I would want to do something like this, but this specific code is not working
obj = commonInfo.objects.get(pk=commonInfo_id)
form = commonInfo_form(request.POST)
date = form.cleaned_data['data_collector'] #this line is not working
data_collector = form.cleaned_data['data_collector'] #this line is not working
obj.update(**{'date':date, 'data_collector':data_collector})
any ideas? I feel like it is just those two lines that I need to fix. Or if there is a more pythonic way or built method in Django?
Just validate with isinstance. so like,
if isinstance(request.POST['date'], datetime.date) and isinstance(request.POST['data_collector'], str):
# you might have to use getattr for request.POST here, I'm not sure
# and request.POST['date'] would have to be converted from a string to datetime.date I think
date = request.POST['date']
data_collector = request.POST['data_collector']
obj.update(**{'date':date, 'data_collector':data_collector})
The process for adding a record from a form is different from updating an existing instance. All you need to do differently is indicate which instance to bind the form to when you create it, ex:
obj = commonInfo.objects.get(pk=commonInfo_id)
form = commonInfo_form(request.POST, instance=obj)

Why is get get_queryset() not returning the right results?

I have two models, Recieved_order and order,
class Order(SmartModel):
restaurant = models.ForeignKey(Restaurant,null=True,blank=True,default = None,help_text="The restaurant the customer order from")
#contact info
email = models.EmailField(max_length=50,help_text="Needed as alternative")
mobile = PhoneNumberField(max_length=20,default='+25078######')
class Recieved_Order(SmartModel):
item = models.ForeignKey(Item)
date_added = models.DateTimeField(auto_now=True,auto_now_add=True)
quantity = models.IntegerField(default=0)
price = models.DecimalField(max_digits=9,decimal_places=2)
order = models.ForeignKey(Order)
i want a restaurant manager(user), to be able to receive orders(Recieved_order) made to his specific restaurants when logged in, to achieve this, i have the following in views.py
class Recieved_OrderCRUDL(SmartCRUDL):
model = Recieved_Order
actions = ('create','read','update','delete','list')
permissions = True
class List(SmartListView):
fields = ('order_email','order_mobile','order_billing_city','item.name','item.price','quantity','order_id','order_restaurant')
search_fields = ('date_added',)
def get_queryset(self,*args,**kwargs):
queryset = super(Recieved_OrderCRUDL.List, self).get_queryset(*args,**kwargs)
if self.request.user.is_superuser:
return queryset
return queryset.filter(order=self.request.user)
with the above i am testing on two different restaurants, the restaurant and its not working out as it should. its returning the wrong orders for a given restaurant.
What am i not doing right with get_queryset().
There's something confusing going on here:
return queryset.filter(order=self.request.user)
You're telling it to build a query that filters Order objects against User objects.
Is there something missing in your sample code that ties orders back to users such that a proper join can be constructed?
If you want to have a user (what you refer to as a manager) only able to view their own orders, you need to change things... Restaurant will need to have a field that points to a User (let's call it user and assume it's a ForeignKey) Then you can do something like
if self.request.user.is_superuser:
return queryset
return queryset.filter(order__restaurant__user=self.request.user)
As pointed out by #Joe Holloway, you should not be trying to filter on the order field with a user object...
The other odd thing I wanted to point out is
fields = ('order_email','order_mobile','order_billing_city','item.name','item.price','quantity','order_id','order_restaurant')
You appear to be using a mixture of ways to attempt to access things...
You should be using __ (that's 2 underscores) to access relations, not _ or .