Django : Updating model by overriding save method - python

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.

Related

Failed to save in SQLite database django

I have the following codes:
models.py
class Job(models.Model):
jobname = models.CharField(max_length = 1000)
owner = models.CharField(max_length = 150)
enabled = models.BooleanField()
freq_type = models.IntegerField(default = 1)
freq_interval = models.IntegerField(default = 0)
freq_recurrence = models.IntegerField(default = 0)
start_date=models.CharField(max_length=10)
end_date=models.CharField(max_length=10, blank = True)
start_time=models.CharField(max_length=6)
end_time=models.CharField(max_length=6, blank = True)
date_added = models.DateTimeField(auto_now_add = True, null = True)
date_modified=models.DateTimeField(auto_now = True, null = True)
version=models.IntegerField(default = 1)
class Job_removed(models.Model):
jobname = models.CharField(max_length = 1000)
owner = models.CharField(max_length = 150)
enabled = models.BooleanField(null = True)
freq_type = models.IntegerField(default = 1)
freq_interval = models.IntegerField(default = 0)
freq_recurrence = models.IntegerField(default = 0)
start_date=models.CharField(max_length=10)
end_date=models.CharField(max_length=10, blank = True)
start_time=models.CharField(max_length=6)
end_time=models.CharField(max_length=6, blank = True)
date_added = models.DateTimeField(null = True)
date_modified=models.DateTimeField(default=timezone.now)
version=models.IntegerField(null=True)
views.py
def job_delete(request,pk):
job=Job.objects.get(pk=pk)
jobdetail = Job_detail.objects.get(job=pk)
if request.method == "POST":
jobr = JobRemovedForm(request.POST)
if jobr.is_valid():
jobr.jobname = job.jobname
print(jobr.jobname)
jobr.owner = job.owner
print(jobr.owner)
jobr.enabled = job.enabled
print(jobr.enabled)
jobr.start_date = job.start_date
print(jobr.start_date)
jobr.start_time = job.start_time
print(jobr.start_time)
jobr.date_added = job.date_added
print(jobr.date_added)
jobr.version = job.version
print(jobr.version)
jobr.save()
return redirect('/job/', {'job':Job.objects.all})
else:
jobr = JobRemovedForm()
return render(request, 'interface/job_removed.html', {'job':job, 'jobdetail':jobdetail, 'jobr':jobr})
return render(request, 'interface/job_removed.html', {'job':job, 'jobdetail':jobdetail})
Output of my powershell for those print commands:
In the database (SQLite):
What I am trying to do is to copy from the entry from Job Table to Job_removed Table. I assign the new entry in Job_removed with the values in Job Table. It is printing correctly in my powershell but when I check my database, none of the value are entering. Why is this happening though? Can anyone explain to me and point me to the right direction to correct this? I know there are post about how to clone data to another table but it does not fit the task that I am required to do so I am not using those answers.
Update: model for Job_detail and form for JobRemovedForm
models.py
class Job_detail(models.Model):
job_type=models.IntegerField(default=1)
json = models.CharField(max_length = 1000)
job = models.ForeignKey(Job, on_delete=models.CASCADE)
forms.py
class JobRemovedForm(ModelForm):
class Meta:
model = Job_removed
fields = []
Update 2: views (I realize i didnt do commit=False) and form fields updated
views.py
def job_delete(request,pk):
job=Job.objects.get(pk=pk)
jobdetail = Job_detail.objects.get(job=pk)
if request.method == "POST":
jobr = JobRemovedForm(request.POST)
if jobr.is_valid():
jr = jobr.save(commit=False)
jr.jobname = job.jobname
print(jr.jobname)
jr.owner = job.owner
print(jr.owner)
jr.enabled = job.enabled
print(jr.enabled)
jr.start_date = job.start_date
print(jr.start_date)
jr.start_time = job.start_time
print(jr.start_time)
jr.date_added = job.date_added
print(jr.date_added)
jr.version = job.version
print(jr.version)
jr.save()
return redirect('/job/', {'job':Job.objects.all})
else:
print(jobr.errors)
jobr = JobRemovedForm()
return render(request, 'interface/job_removed.html', {'job':job, 'jobdetail':jobdetail, 'jobr':jobr})
return render(request, 'interface/job_removed.html', {'job':job, 'jobdetail':jobdetail})
forms.py
class JobRemovedForm(ModelForm):
class Meta:
model = Job_removed
fields = ['jobname', 'owner', 'enabled', 'start_date', 'start_time', 'date_added', 'version']
And now my powershell is showing jobr.errors of the following:
jobnameThis field is required.ownerThis field is required.start_dateThis field is required.start_timeThis field is required.date_addedThis field is required.versionThis field is required.
Actually you do not need Job_removed model because it is unuseful and not better design for a such case in general.so first remove that model and add a field called is_deleted to your job model which value should be True for deleted jobs and False for non deleted jobs.by default i make is_deleted is False so when you deleted it you can mark it as True
class Job(models.Model):
jobname = models.CharField(max_length = 1000)
owner = models.CharField(max_length = 150)
enabled = models.BooleanField()
freq_type = models.IntegerField(default = 1)
freq_interval = models.IntegerField(default = 0)
freq_recurrence = models.IntegerField(default = 0)
start_date=models.CharField(max_length=10)
end_date=models.CharField(max_length=10, blank = True)
start_time=models.CharField(max_length=6)
end_time=models.CharField(max_length=6, blank = True)
date_added = models.DateTimeField(auto_now_add = True, null = True)
date_modified=models.DateTimeField(auto_now = True, null = True)
version=models.IntegerField(default = 1)
is_delete = models.BooleanField(default=False) # new field
Delete your model remove Job_removed
2)run python manage.py makemigrations
3)run python manage.py migrate
now let us work on your views for deleting jobs.
from django.shortcuts import render,get_object_or_404,redirect
def job_delete(request,pk):
job= get_object_or_404(Job,pk=pk,is_deleted=False)
job.is_deleted = True # delete the job if it is not deleted
job.save()
return redirect('/job/')
Note:I use get_object_or_404 to raise page not found if there is no job related to the pk and i check that the job is not deleted.
now i do not know how is your other views but you should now make a little bit of change in querying jobs.if you want to query all jobs you should query jobs that is not deleted.by doing this
Job.objects.filter(is_deleted = False)
instead of
Job.objects.all()
and better approach should be to use post method for deleting jobs not get.but for now you can keep as it is.sorry for my english if you do not understand please ask me in the comments.
Update but if you want to do is it as you did.
then you do not need a form you can just copy the data from Job to Job_removed.and if you need a form you can make these fields read only.
let us do it without form
from django.shortcuts import render,get_object_or_404,redirect
def job_delete(request,pk):
job= get_object_or_404(Job,pk=pk)
jr = Job_removed()
jr.jobname = job.jobname
print(jr.jobname)
jr.owner = job.owner
print(jr.owner)
jr.enabled = job.enabled
print(jr.enabled)
jr.start_date = job.start_date
print(jr.start_date)
jr.start_time = job.start_time
print(jr.start_time)
jr.date_added = job.date_added
print(jr.date_added)
jr.version = job.version
print(jr.version)
jr.save()
job.delete() # then delete the job
return redirect('/job/')
Form is not required in this situation.
Soft delete is the best option. Have a look in to this tutorial

Django model form field to have a user dropdown list based on a condition

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__'

Multiple django model values depend on choice field value

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)

Designing a model for vehicle entry in Django

I am looking design a simple app that logs number of vehicles that enter through a point with their details.Then generate a report of different services /vehicles / etc . I have come up with a sample model.
from django.db import models
# Create your models here.
class Service(models.Model):
service_name = models.CharField(max_length = 60)
timestamp = models.DateTimeField(auto_now_add = True,auto_now = False)
def __unicode__(self):
return self.service_name
class Place(models.Model):
place_name = models.CharField(max_length = 120)
state = models.CharField(max_length=60)
timestamp = models.DateTimeField(auto_now_add = True,auto_now = False)
def __unicode__(self):
return self.place_name
class Connection(models.Model):
vehicle_no = models.CharField(max_length = 15)
service= models.ForeignKey(Service)
source = models.ForeignKey(Place,related_name = 'source')
destination = models.ForeignKey(Place,related_name = 'destination')
trip_distance = models.PositiveIntegerField()
entry_timestamp = models.DateTimeField(auto_now_add = True,auto_now = False)
def __unicode__(self):
return self.vehicle_no
class GlobalOption(models.Model):
config_option = models.CharField(max_length=120)
value = models.CharField(max_length = 60)
Admin.py
from django.contrib import admin
from .models import Connection,Service,Place,GlobalOption
from .forms import ConnectionForm
# Register your models here.
class ConnectionAdmin(admin.ModelAdmin):
form = ConnectionForm
list_display = ('vehicle_no','service','source','destination','trip_distance','Connection_timestamp')
list_filter = ['Connection_timestamp']
search_fields = ['service__service_name','vehicle_no']
class OptionAdmin(admin.ModelAdmin):
fields = ['config_option','value']
list_display = ('config_option','value')
class ConnectionInline(admin.TabularInline):
model = Connection
extra = 1
class PlaceAdmin(admin.ModelAdmin):
list_display = ['place_name','timestamp']
class Meta:
Model = Place
class ConnectionInline(admin.TabularInline):
model = Connection
extra = 1
class ServiceAdmin(admin.ModelAdmin):
list_display = ['service_name','timestamp']
class Meta:
Model = Service
inlines = [ConnectionInline]
admin.site.register(Connection,ConnectionAdmin)
admin.site.register(Service,ServiceAdmin)
admin.site.register(Place,PlaceAdmin)
admin.site.register(GlobalOption,OptionAdmin)
However in the Admin , Whenever i add a connection, It is possible to have same source and destination locations. I do not want that. Also how would it be possible to dynamically generate the list of choices on destinations after selecting a source ?
Since, there will only be incoming connections on this app, Would a better design decision would be to have separate models for Sources and destinations?
If you don't want the same source/destination to be selected, you could handle that in the clean() method for your form.
You could generate choices & cache them based on your source/destination models, your design looks ok, but keep in mind the option to create the separate models. I do similar to allow choices based on the values in various columns of a table;
class GetResults(forms.ModelForm):
#staticmethod
def get_choices(event_year):
key = FORM_CHOICES_CACHE_KEY.format(
year=event_year
)
choices = cache.get(key)
if choices:
return choices
age_categories = set()
events = set()
for age_category, event in Result.objects.values_list('age_group', 'event').distinct():
if age_category:
age_categories.add(age_category)
if event:
events.add(event)
age_categories = [
(ac, ac) for ac in sorted(age_categories, key=lambda a: a.lower())
]
events = [
(e, e) for e in sorted(events, key=lambda a: a.lower())
]
choices = (
age_categories,
events
)
cache.set(key, choices, FORM_CHOICES_CACHE_LENGTH)
return choices
def __init__(self, event_year, *args, **kwargs):
self.event_year = event_year
if not self.event_year:
self.event_year = datetime.datetime.utcnow().year
age_categories, events = self.get_choices(event_year)
super(GetResults, self).__init__(*args, **kwargs)
self.fields['age_group'] = forms.ChoiceField(choices=age_categories)
self.fields['age_group'].label = _("Age category")
self.fields['event'] = forms.ChoiceField(choices=events)
self.fields['event'].label = _("Event")
class Meta:
model = Result

Django Many To Many Complaining about PrimaryKey

I have three models that are related. The first is called DayOfWeek, which is juts a day label and number. It looks like this:
class DayOfWeek(models.Model):
day = models.IntegerField()
label = models.CharField(max_length='20')
def __str__(self):
return self.label
This class is populated using a fixture every time I syncdb.Next, I have an event model, it looks like this:
class Event(AnnouncementBase, Location):
cost = CurrencyField(decimal_places=2, max_digits=10, blank=True, default=0.00)
start_date = models.DateField(default = datetime.now().date())
start_time = models.TimeField(default = datetime.now().time())
end_date = models.DateField(blank=True, default=None, null = True)
end_time = models.TimeField(blank=True, default=None, null = True)
Finally, there is a recurrence. It has an event and is used to schedule the event for recurring events. It looks like this:
class Recurrence(models.Model):
event = models.ForeignKey(Event, related_name='event')
repeats = models.CharField(max_length = 50, choices = EVENT_REPEAT_CHOICES)
repeat_every = models.IntegerField(default = 1)
repeat_on = models.ManyToManyField(DayOfWeek, blank=True, null=True)
repeat_by = models.CharField(max_length = 50, choices = EVENT_REPEAT_BY_CHOICES, blank=True)
repeat_by_day_of_month = models.IntegerField(default = 0, blank=True)
repeat_ends = models.CharField(max_length = 50, choices = EVENT_REPEAT_END_CHOICES)
end_occurrences = models.IntegerField(default = 0, blank=True)
repeat_end_date = models.DateField(blank=True, default=None, null = True)
past_event_count = models.IntegerField(default=0, blank=True)
scheduled_events = models.ManyToManyField(Event, blank=True, default=None, related_name = 'scheduled_events')
is_active = models.BooleanField(blank=True, default=True)
def save(self, force_insert=False, force_update=False, using=None):
"""Overridden to create events the first time."""
self.full_clean()
#First do normal save so the data is there for the even scheduler.
self.save_base(force_insert=force_insert, force_update=force_update, using=using)
#If nothing is scheduled yet, schedule the first batch
if self.scheduled_events.count() == 0 and self.past_event_count == 0:
self.scheduleEvents()
def clean(self):
#repeat on weekly
if self.repeat_every < 1:
raise ValidationError('Repeat every must be at least 1.')
#weekly
if self.repeats == EVENT_REPEAT_CHOICES[1][0]:
#look for missing stuff
if not self.repeat_on:
raise ValidationError('Missing repeat on.')
Finally, I have a unit test that checks this works ok it looks like this:
def test_weekly_mon_wed_fri_occurrence(self):
event = Event()
event.start_date = date(year=2012, month=1, day=2)
event.start_time = time(hour=13, minute=30)
event.save()
recurrence = Recurrence()
recurrence.repeats = EVENT_REPEAT_CHOICES[1][0]
recurrence.repeat_on = (EVENT_DAY_CHOICES[1][0], EVENT_DAY_CHOICES[3][0], EVENT_DAY_CHOICES[5][0])
recurrence.repeat_ends = EVENT_REPEAT_END_CHOICES[0][0]
recurrence.event = event
nextEvent = recurrence.getNextEvent(event)
self.assertEquals(date(year=2012, month=1, day=4), nextEvent.start_date)
self.assertEquals(event.start_time, nextEvent.start_time)
nextNextEvent = recurrence.getNextEvent(nextEvent)
self.assertEquals(date(year=2012, month=1, day=6), nextNextEvent.start_date)
self.assertEquals(event.start_time, nextNextEvent.start_time)
Whenever the test is run it fails, with the following exception.
ValueError: 'Recurrence' instance needs to have a primary key value before a many-to-many relationship can be used.
The error happens on the line if self.repeat_on in the clean method.
I want repeat_on to be optional, only some types of recurrences need it. How do I make this work? What am I missing that is causing it to fail?
You need to call recurrence.save() before assigning Many2Many relationships. In your code you do
recurrence.repeat_on = (EVENT_DAY_CHOICES[1][0], EVENT_DAY_CHOICES[3][0], EVENT_DAY_CHOICES[5][0])
without saving the recurrence first. Because its not saved, recurrence does not have primary key generated yet, and Django ORM doesn't know what to insert as a foreign key into the M2M table.

Categories