I have the following model
class customer_prices(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE,null=True)
price = models.FloatField()
image = models.FloatField()
function = models.TexField()
.
.
(10 more fields)
The fields shown in the homepage is only price thus the other fields are assigned manually, based on the values in a dataframe returned by a self-written some_util_function() e.g
#login_required
def add_price(request):
cleaned_data = form.cleaned_data
df = some_util_function(cleaned_data["price"])
form.user_id = user.id
form.image= df["image"]
.
.
form.save()
messages.success(request, "Price added")
return redirect("my_html")
return render(...)
As we can see each form-field is updated by form.field=df["field"] thus I wonder, if there is another (better way) of doing that?
I have thought about the two following ways:
Instead of form.field=value then form.cleaned_data["field"] = value
Since I have all the data in a dataframe, simply push that dataframe to the given DB e.g df.to_sql("my_table_name").
I know (2) is working, but I think it's better to keep the database changes to Django, and not some mix between pushing data self and let Django handle it.
Related
I am trying to make a warehouse management system with Django 3.2 based on this models:
class itemtype(models.Model):
item_id = models.IntegerField(primary_key=True)
item_name = models.CharField(max_length=100)
group_name = models.CharField(max_length=100)
category_name = models.CharField(max_length=100)
mass = models.FloatField()
volume = models.FloatField()
used_in_storage = models.BooleanField(default=False, null=True)
class Meta:
indexes = [
models.Index(fields=['item_id'])
]
def __str__(self):
return '{}, {}'.format(self.item_id, self.item_name)
class material_storage(models.Model):
storage_id = models.AutoField(primary_key=True)
material = models.ForeignKey(itemtype, on_delete=models.PROTECT)
amount_total = models.IntegerField(null=True)
price_avg = models.FloatField(null=True)
order_amount = models.IntegerField(null=True)
order_price = models.IntegerField(null=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
return '{}, {} avg.: {} ISK'.format(self.material, self.amount, self.price)
"itemtype" defines basically the possible objects which could be stored and "material_storage" shows what is in stock. I tried to combine the total amount of every item as well as the average price paid for it and the amount and price for a single order in the same database row. The idea is to get the last record for the chosen item/material when a new order happens, add the amount of that order and recalculate the avg price.
Theoretically this could be split up on two tables, but I don't see a reason to do so at the moment.
However, I am not able to figure out the actual function code to do the calculations. I am new to Django and therefor a bit overwhelmed by the complexity. I tried to use class based views and model forms, for the easy stuff that worked fine but now I am kind of lost.
Making a form just for adding new rows to that storage table was ok.
class NewAssetForm(forms.ModelForm):
material = MaterialChoiceField(models.itemtype.objects.filter(used_in_storage= True))
def __init__(self, *args, **kwargs):
super(NewAssetForm, self).__init__(*args, **kwargs)
self.fields['amount'].widget.attrs['min'] = 1
self.fields['price'].widget.attrs['min'] = 1
class Meta:
model = models.material_storage
fields = (
'material',
'amount',
'price'
)
widgets = {
'material': forms.Select(),
}
Same for the View to process it.
class NewItemView(FormView):
template_name = 'assetmanager/newasset.html'
form_class = forms.NewAssetForm
success_url = '/storage/current'
def form_valid(self, form):
return super().form_valid(form)
But now I am stuck. I thought this should be a fairly standard task, but I couldn't find a solution for it by now. The Idea was to put it in the form_valid function, take the material from the form to find the latest relevant record, add the new amount as well as calculate the new average price and save all together to the model. So far i only found a few examples comparable with my problem at all and I wasn't able to translate them to my setup, so maybe someone can give me a hint for a more successful search or provide me an example how to approach this topic.
thx in advance.
To modify the values of the form fields, you can override "clean" method and provide values to the form fields. Data can be accessed using "self.cleaned_data", it is a dictionary.
class NewAssetForm(ModelForm):
def clean(self):
super().clean()
# place code that retrieves existing data and calculate new values.
self.cleaned_data['price'] = 'New Value'
cleaned_data will be passed to "form_valid", there you can call the save function. "form.save()" will create a new row, make sure you are passing valid values to the views. Since you are accepting few fields in the form, make sure you have default values for the fields that are not included in the form object.
Thank you for your answer I found a solution by using the form_valid() method within the FormView. The majority of the code is used to create entries based on the existing entries or to check whether there are already entries for the same material.
class NewItemView(FormView):
template_name = 'assetmanager/newasset.html'
form_class = forms.NewAssetForm
success_url = '/storage/current'
def form_valid(self, form):
try:
# check if there is already a record for this material.
material_storage.objects.filter(material_id = form.cleaned_data['material'])[:1]
# getting total amount and average price values from latest entry with said material.
total_amount = material_storage.objects.values('amount_total').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['amount_total']
avg_price = material_storage.objects.values('price_avg').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['price_avg']
amount = form.cleaned_data['amount']
price = form.cleaned_data['price']
# calculating new total amount and average price based on old values and new entry.
form.instance.amount_total = total_amount + amount
form.instance.price_avg = ((avg_price * total_amount) + (price * amount)) / (total_amount + amount)
form.save()
except material_storage.DoesNotExist:
# if there is no entry for the chosen material yet, amount = total amount, price = average price.
form.instance.amount_total = form.cleaned_data['amount']
form.instance.price_avg = form.cleaned_data['price']
form.save()
return super().form_valid(form)
For now this solves my problem, however I don't know if the chosen location (form_valid()) makes sense - your answer suggests it would make more sense elsewhere.
Also, checking if an entry already exists for the material and selecting values from such an entry are pretty sure not very elegant and efficient. But as already mentioned, I am a beginner - I would be happy about any suggestions for improvement.
I am also not sure yet if this handles every probable special case which could appear...
I am facing the problem of adding custom permission in Django. My idea is that when authorizing a user, if it belongs to group1, only the rows of group1 are displayed, and otherwise the rows of group2. There is a group field in the database table, it contains only two values group1 and group2. In the admin panel, I created 2 groups, respectively, and also included users in these groups. Also I created permissions and assigned them to each user.
My models.py
class Employee(models.Model):
DEPT =(
('Group1', 'Group1'),
('Group2', 'Group2')
)
dept = models.CharField(max_length=100, choices=DEPT, default='')
fio = models.CharField(max_length = 100)
work_place = models.CharField(max_length = 150, default= '')
def __str__(self):
return self.fio
class Meta:
permissions = (
("view_museum", "can view museum"),
("view_ssoismi", "can view ssoismi"),
)
My views.py
def employee_list(request):
context = {'employee_list': Employee.objects.all()}
return render(request, "employee_register/employee_list.html", context)
If i change the context row to
context = {'employee_list': Employee.objects.filter(dept="Group1")}
I get the desired result, but how can do it automatically with django groups and etc.
Maybe i do something wrong?
Any idea what to do next?
Thanks
In the admin panel, I created 2 groups, respectively, and also included users in these groups.
Query these group names by filtering request.user.groups with name__in. Then filter Employee.objects by those results with dept__in.
Note that this assumes the admin group names are named exactly like the Employee.dept field. If not, rename the admin groups to match the Employee.dept field (including capitalization, spaces, etc.).
def employee_list(request):
groups = [g.name for g in request.user.groups.filter(name__in=['Group1', 'Group2')]
context = {'employee_list': Employee.objects.filter(dept__in=groups)}
return render(request, 'employee_register/employee_list.html', context)
Try this:
context = {'employee_list': Employee.objects.filter(dept=request.user.group)}
I'm not sure how you made the User belong in one of the groups but I'm guessing you also have a group field on your User model.
I have 2 models employee and leave, where Employee is the foreign key in Leave. I want to display the leave requests by a specific employee on a page when they log in.
i'm not able to get the leave data populated on, and if there are more than 2 leaves applied by a single employee i get an error saying 2 items found
here is my code
models.py
class Employee(models.Model):
user=models.OneToOneField(User, on_delete=models.CASCADE)
no_of_leaves=models.IntegerField(
null=False,
validators=[
MinValueValidator(1),
MaxValueValidator(24)
],
default=24
)
def __str__(self):
return self.user.username
class Leave(models.Model):
employee=models.ForeignKey(Employee,on_delete=models.CASCADE,default="")
start_date=models.DateField(auto_now_add=False)
end_date=models.DateField(auto_now_add=False)
req_date=models.DateTimeField(default=datetime.datetime.now())
STATUS_OPTIONS = (
("Approve","Approve"),
("Pending","Pending"),
("Decline","Decline"),
)
approved=models.CharField(max_length=10,choices=STATUS_OPTIONS,default='Pending')
def __str__(self):
return self.employee.user.username
#property
def date_diff(self):
return (self.start_date - self.end_date).days
views.py
def home(request):
user = request.user
u = User.objects.get(username=user)
e = Employee.objects.get(user=user.id)
leave = Leave.objects.get_or_create(employee=e)
print(leave)
nofleaves=None
if user.is_superuser:
pass
else:
nofleaves=u.employee.no_of_leaves
context={'nofleaves':nofleaves,'leave':leave,}
return render(request,"leaveApp/home.html",context)
Just like #Jao said get is to fetch only one data while there is a many to one relationship between Leave and Employee that means there can be multiple Leave for one employee thus get will throw an error.
You should use filter:
Change this line
leave = Leave.objects.get_or_create(employee=e) to something like
leave = Leave.objects.filter(employee=e.id)
if not leave.exists():
leave = Leave.objects.create(employee=e)
This other question might help.
The thing is you should use get for only one data. You shouldn't use get on a many-to-one relathionship. The logic is, there can be multiple Leave by Employee so you can't have a consistent use of get.
What you can use is filter but I'd use related names/related fields which will allow you to use employee.leaves if you define it correctly.
Unable to perform the update operation.
This function is for table update, but the code is not allowing me to pass the instance in the form because it is not a model form. Please suggest the changes.
class userForm(forms.Form):
SHIFT_CHOICES = ( ('D','DAY'), ('N','NIGHT') )
ADMISSION_FORM_STATUS = ( ('Y','YES'), ('N','NO') )
FORM_COMPLETE_STATUS = ( ('Y','YES'), ('N','NO'))
TRAINING_STATUS = ( ('Y','YES'), ('N','NO') )
STATUS = ( ('W','WORKING'), ('OL','ON_LEAVE'), ('E','EXIT') )
employee_id = forms.CharField(max_length=8,required=False)
employer_id = CompanyModelChoiceField(required=False, queryset=Company.objects.all(), label='Employer', widget=Select2Widget)
name = forms.CharField(max_length=255)
uber_name = forms.CharField(max_length=255, required=False)
mobile = forms.CharField(max_length=20, required=False)
To bind data with the form fields, (not a model form), you will have to pass a dictionary with those specific fields to the form constructor.
In this case what you need to do is
Get the form data in a dictionary
driver = Driver.objects.get(employee_id = employee_id)
form_data = {'employee_id':driver.employee_id, 'employer_id', driver.employer_id, .., .., .} #all the fields in the form
AddDriverForm(initial=form_data)
Then while saving the data you should do it the same way you did for creating the driver.
Moreover, I would personally suggest you to use Model form for this case since you are going to put that data in the model anyways, it will surely save you the hassle.
Also, you might consider to normalize your model if its not a big deal.
Lets say your model (DC) has an instance (an entry):
Superhero = Bruce, Butler = Alfred, Engineer = Lucius, car = Batmobile
Your form has
Superhero, Butler, Engineer
(Notice that my form is missing the car field)
Now, suppose you want to change the name Butler to Jarvis
instance = DC.objects.get(id=8) #lets consider this as the id of the instance we have
#what you need to do is save the required data in a dictionary in this case
form_data = {'Superhero':instance.Superhero, 'Butler':instance.Butler, 'Engineer': instance.Engineer } #we haven't passed the car field
#Also I think that passing extra fields must not be an issue but I haven't tried
UpdateForm(form_data)
This will render the data properly when the form is displayed.
I am trying to figure out how to dynamically change a ModelForm field based on the input from a previous field.
For example, if I have these kinds of models:
class Phone(models.Model):
name = models.CharField(max_length=10)
class Series(models.Model):
name = models.CharField(max_length=10)
class Manufacturer(models.Model):
phone = models.ForeignKey('Phone')
series = models.ForeignKey('Series')
class ManufacturerForm(ModelForm):
class Meta:
model = Manufacturer
Which would generate a form (ManufacturerForm) with dropdown options for the phone and series entries in the database. Is it possible to a different set of series entries based on the phone entered by the user, for example?
I have read about using the __init__ method to accomplish this, based on what I have read on this blog post, but I am not sure how to execute this given my scenario. Or maybe there is a better way to go about achieving this that you have taken? Thanks for any advice!
EDIT: Added the form's view.
def make_ad(request):
if request.method == 'POST':
form = ManufacturerForm(request.POST, request.FILES)
if form.is_valid():
a = form.save()
a.user = request.user
a.save()
else:
form = ManufacturerForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('place.html', variables)
#super9 mentioned using ajax request to change these elements. I need to check if request.is_ajax(), but at what point should I check this in my view? And how do I add or change the queryset based on the ajax request?
Thanks for your advice.
EDIT: Trying to use django-smart-selects
Not sure how to setup my models to utilize django-smart-selects to accomplish what I am trying. Here is how I have structured my models:
from smart_selects.db_fields import ChainedForeignKey
class Phone(models.Model):
phone = models.CharField(max_length=10)
class Series(models.Model):
series = models.CharField(max_length=10)
phone = models.ForeignKey(Phone)
class SeriesModel(models.Model):
model = models.CharField(max_length=10)
series = models.ForeignKey(Series)
class Manufacturer(models.Model):
phone = models.ForeignKey(Phone)
series = ChainedForeignKey (Series, chained_field = "phone", chained_model_field = "phone")
series_model = ChainedForeignKey (SeriesModel, chained_field = "series", chained_model_field = "series")
But when I view my form (ModelForm) the fields for series_model are not chained properly to series. Am I missing something to make smart-selects work on the second layer of abstraction?
EDIT: Above code now works.