Building my first Django app, and I'm running into a snag. I have a Django model that creates Job objects, and I want each job code to be unique and auto-generated, with a particular format. The format is: aaaMMnnYYYY, where aaa is a 3-letter client identifier that we set, nn is a counter that represents the nth job from that client in that month., and MM and YYYY are month and year respectively.
e.g., for the 3rd job from client "AIE" in feb 2023, the ID would be AIE02032023.
Using a calculated property with the #property decorator causes the field to be updated with every save, so I'm trying to do this by modifying the save() method. There's also a related Cost object that has a Job attribute as a Foreign Key. The way I have it now, the job code gets assigned as expected, but when I add a Cost to the Job, the 'iterating' part of the job code iterates, changing the job code, which causes uniqueness errors as well as URL errors (I'm using the job code in the URLConf. Is there any way to have this field get calculated once and then never change?
As a side note, I'd also like to be able to override the job code. Is there a way to set flags within a model, such as job_code_overridden = False, etc.?
Here's the relevant code, let me know what else you need to see.
models.py:
class Job(models.Model):
job_name = models.CharField(max_length=50, default='New Job')
client = models.ForeignKey(Client, on_delete=models.CASCADE)
job_code = models.CharField(max_length=15, unique=True,)
def get_job_code(self):
'''
I only want this to run once
Format abcMMnnYYYY
'''
jc = ''
prefix = self.client.job_code_prefix
month = str(str(self.job_date).split('-')[1])
identifier = len(Job.objects.filter(job_date__contains = f'-{month}-',
client__job_code_prefix = prefix)) + 2
year = str(str(self.job_date).split('-')[0])
jc = f'{prefix}{month}{identifier:02d}{year}'
return jc
#property
def total_cost(self):
all_costs = Cost.objects.filter(job__job_code = self.job_code)
total = 0
if all_costs:
for cost in all_costs:
total += cost.amount
return total
# Is there a way to add something like the flags in the commented-out code here?
def save(self, *args, **kwargs):
# if not self.job_code_fixed:
if self.job_code != self.get_job_code():
self.job_code = self.get_job_code()
# self.job_code_fixed = True
super().save(*args, **kwargs)
costsheet.py:
class costsheetView(ListView):
template_name = "main_app/costsheet.html"
form_class = CostForm
model = Cost
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
current_job_code = self.kwargs['job_code']
currentJob = Job.objects.get(job_code=current_job_code)
return context
def get(self, request, *args, **kwargs):
cost_form = self.form_class()
current_job_code = self.kwargs['job_code']
currentJob = Job.objects.get(job_code=current_job_code)
all_costs = Cost.objects.filter(job__job_code = current_job_code)
return render(request, self.template_name, {'cost_form':cost_form, 'currentJob':currentJob,'all_costs':all_costs})
def post(self, request, *args, **kwargs):
cost_form = self.form_class(request.POST)
current_job_code = self.kwargs['job_code']
currentJob = Job.objects.get(job_code=current_job_code)
messages = []
errors = ''
if cost_form.is_valid():
instance = cost_form.save()
instance.job = currentJob
instance.save()
currentJob.vendors.add(instance.vendor)
currentJob.save()
messages.append(f'cost added, job date: {currentJob.job_date}')
else:
print('oops')
print(cost_form.errors)
errors = cost_form.errors
all_costs = Cost.objects.filter(job__job_code = current_job_code)
return render(request, self.template_name, {'cost_form':cost_form,
'currentJob':currentJob,
'errors':errors,
'messages':messages,
'all_costs':all_costs,
})
Lastly, in the save() method I know I could do something like
if job_code != get_job_code():
job_code = get_job_code()
..but the job 'month' often changes throughout the life of the job, and if I run get_job_code() after the month changes then the job code will change again, which is undesirable.
A possible solution is to add an additional flag field, job_code_fixed, to the Job model. The job_code should only be generated once when the Job object is created, and not on subsequent saves. This can be achieved by setting job_code_fixed to True after the job_code is generated in the save method, and only generating the job_code if the job_code_fixed is False.
Here is an updated version of the code:
class Job(models.Model):
job_name = models.CharField(max_length=50, default='New Job')
client = models.ForeignKey(Client, on_delete=models.CASCADE)
job_code = models.CharField(max_length=15, unique=True,)
job_code_fixed = models.BooleanField(default=False)
def get_job_code(self):
'''
I only want this to run once
Format abcMMnnYYYY
'''
jc = ''
prefix = self.client.job_code_prefix
month = str(str(self.job_date).split('-')[1])
identifier = len(Job.objects.filter(job_date__contains = f'-{month}-',
client__job_code_prefix = prefix)) + 2
year = str(str(self.job_date).split('-')[0])
jc = f'{prefix}{month}{identifier:02d}{year}'
return jc
#property
def total_cost(self):
all_costs = Cost.objects.filter(job__job_code = self.job_code)
total = 0
if all_costs:
for cost in all_costs:
total += cost.amount
return total
def save(self, *args, **kwargs):
if not self.job_code_fixed:
self.job_code = self.get_job_code()
self.job_code_fixed = True
super().save(*args, **kwargs)
By adding the job_code_fixed flag, the job_code will only be generated once and never changed, solving the problem with changing job codes causing uniqueness and URL errors.
Related
models.py -- there i create leave_links 10g field and want calculate him like substract by_projects_10g with on_facts_10g
class KT(models.Model):
by_projects_10g = models.CharField(max_length=255)
by_projects_100g = models.CharField(max_length=255)enter code here
on_facts_10g = models.CharField(max_length=255)
on_facts_100g = models.CharField(max_length=255)
#now I want do like this, but an error comes out
leave_links_10g = models.IntegerField(default=0)
def calculate_leave_links(a,b):
leave_links_10g = a -b
return leave_links_10g
def query_links(self):
calculate_leave_links(self.by_projects_10g, self.on_facts_10g)
#####views.py
def index(request):
KT_links = KT.objects.all().values()
template = loader.get_template('index.html')
context = {
'KT_links': KT_links,
}
return HttpResponse(template.render(context, request))
if you don't need to do db query on field leave_links_10g i sugest use #property:
#property
def leave_links_10g(self):
try:
return int(self.by_projects_10g) - int(elf.on_facts_10g)
except ValueError:
return 0
you can acces it like normal field e.g.:
kt_object.leave_links_10g
but if you want to do some db operations like filter or order_by you need to override save method of model KT:
def save(self, *args, **kwargs):
try:
self.leave_links_10g = int(self.by_projects_10g) - int(elf.on_facts_10g)
except ValueError:
self.leave_links_10g = 0
super().save(*args, **kwargs) # Call the "real" save() method.
I have to do basic unit testing in order to get my code coverage higher. I do not need to do anything fancy. Just the bare minimum is required. So, in this case if I am able to hit my view successfully, then that is all I need to do.
Agile team is nested under Team as you can see in the models.py.
models.py -
class Organization(models.Model):
orgname = models.CharField(max_length = 100, blank=True)
def __str__(self):
return str(self.orgname)
class Team(models.Model):
teamID = models.AutoField(primary_key=True)
teamName = models.CharField(max_length = 100, blank=True)
org = models.ForeignKey(Organization, on_delete=models.CASCADE)
def __str__(self):
return str(self.teamName)
class AgileTeam(models.Model):
agileTeamID = models.AutoField(primary_key=True)
agileTeamName = models.CharField(max_length = 100, blank=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
def __str__(self):
return str(self.agileTeamName)
views.py -
#csrf_exempt
def newATeam(request):
if request.method == 'POST':
param = json.loads(request.body)
agileteam = param.get('agileteam')
teamname = param.get('teamname')
team = Team.objects.get(teamName = teamname)
AgileTeam.objects.create(agileTeamName=agileteam, team=team)
return JsonResponse(status = 200, data = {})
urls.py -
path('ajax/newATeam/', views_admin.newATeam, name='ajax_newATeam'),
test_views.py -
class TestViews(TestCase):
#classmethod
def setUpTestData(self):
# set-up org
ORG_NAMES = ['REVCYCSCH', 'DIABLO', 'BROTHERHOOD']
for org in ORG_NAMES:
Organization.objects.create(orgname=org)
# set-up team
orgModels = Organization.objects.all()
TEAM_NAMES = ['Blitzkrieg ', 'Hammerheads', 'Mercenaries']
for i in range(len(TEAM_NAMES)):
Team.objects.create(teamName=TEAM_NAMES[i], org=orgModels[i])
# set-up agileteam
teamModels = Team.objects.all()
AGILE_TEAM_NAMES = ['Barons', 'Exterminators ', 'Mavericks', 'Renegades', 'Stratosphere', 'Trojans ']
index = 0
for team in teamModels:
for _ in range(2):
AgileTeam.objects.create(agileTeamName=AGILE_TEAM_NAMES[index], team=team)
index = index + 1
self.client = Client()
def test_newATeam(self):
params = {'agileteam': 'Barons', 'teamname': 'Blitzkrieg'}
response = self.client.post(path = '/ajax/newATeam/', data = params, content_type = 'application/json')
self.assertEqual(response.status_code, 200)
However, I get this error -
Error
Thanks in advance!
There are a few things I would do slightly differently.
In views.py:
#csrf_exempt
def new_agile_team(request):
if request.method == 'POST':
if request.META.get('CONTENT_TYPE', '').lower() == 'application/json' and len(request.body) > 0:
try:
body_data = json.loads(request.body.decode('utf-8'))
except Exception as e:
return HttpResponseBadRequest(json.dumps({
'error': 'Invalid request: {0}'.format(str(e))
}), content_type="application/json")
try:
team = Team.objects.get(teamName = teamname)
AgileTeam.objects.create(agileTeamName=agileteam, team=team)
except Team.DoesNotExist as error:
# Handle error here...
return JsonResponse(status = 200, data = {})
However, I would strongly recommend using Djago REST framework for this - as you may need to provide only logged in users the ability to create teams, so you'll need to understand #action level permissions on routes, as well as utilising a more RESTful principle.
So... you are sending 'teamName': 'Blitzkrieg', but then you are fetching teamname = param.get('teamname')... of course it wont work, did you even tried to debug it ?
I see so many problems with this code, that I just don't know from where to start, but just for the beginning start using PEP8 + Djagno Coding styles (+ maybe some linter & formatter) and your life will became much more easier.
I've got a Django models:
class Item_received_log(models.Model):
name = models.CharField(max_length=250)
quantity = models.FloatField(default=1)
class Inventory (models.Model):
name = models.CharField(max_length=250)
quantity = models.FloatField(default=1)
I would like to update Inventory.quantity each time new item to Item_received_log is posted with matching name. I am not sure if it is right but I've decided to override save method of Item_received_log class so it updates Inventory list upon saving:
def save(self, *args, **kwargs):
obj, created = Inventory.objects.update_or_create(
name=self.name,
defaults = {'quantity':(quantity + self.quantity)})
super(Item_received_log, self).save(*args, **kwargs)
And in returns:
NameError at /admin/accountable_persons/item_received_log/17/change/
global name 'quantity' is not defined
How can I resolve my issue or come up with better solution?
Would have been a lot easier if we could simply throw in an F() expression into the default part of update_or_create to do all the magic, but the issue requesting this feature is still open.
You can, however, use a more verbose approach for now:
from django.db.models import F
def save(self, *args, **kwargs):
obj, created = Inventory.objects.get_or_create(name=self.name)
obj.quantity = F('quantity') + self.quantity
obj.save()
super(Item_received_log, self).save(*args, **kwargs)
Hi I am using Django framework for my website. I've a View in project which saves data in to the database (Models). But when I run my program am getting an error as follows
ValueError: invalid literal for int() with base 10: '2015-08-24 12:27:58'
My views.py
class ProcessCheckBinningView(JSONResponseMixin, View):
#model = OrcAwaiverBin
def post(self, request, *args, **kwargs):
status = 'error'
msg = "this is from me"
post_body = json.loads(self.request.body)
fab_value = post_body['fab']
technode_value = post_body['technode']
layer_value = post_body['layer']
print fab_value, technode_value, layer_value
print "submitted from the template f"
bin_object = OrcAwaiverBin()
# Record the last accessed date
bin_object.fab = fab_value
bin_object.technology = technode_value
bin_object.layer = layer_value
print "OKKKKK"
bin_object.created_by = '2015-08-24 12:27:58'
bin_object.date_modified = '2015-08-24 12:27:58'
bin_object.save()
return self.render_json_response(dict(status=status, msg=msg))
class CheckBinningView(TemplateView):
template_name = "orc_enable.html"
def get_context_data(self, *args, **kwargs):
context = super(CheckBinningView, self).get_context_data(*args, **kwargs)
fab = GroupProfile.objects.get(id=self.request.session['ACL_gid']).fab
gp = GroupProfile.objects.get(id=self.request.session['ACL_gid'])
context['fab'] = gp.fab
context['ngapp'] = "CMOD"
return context
My Model fields:
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(blank=True)
Can someone tell me whats the problem here? Thanks in advance.
Based on what the error suggests, You should have declared the bin_object.created_by field or bin_object.date_modified field as IntegerField.
The issue was, as Aswin said my model only accept integer values. I was trying to insert datetime value. Thanks folks!
Am having a challenge i hope you can help me over come.
Am building a django driven application for movie ticket bookings and coming up short on the forms.
When a user clicks on a particular movie, i want to render a page that has a form where the user can choose options for his/her ticket like number of tickets, seat number, date etc.
However, my form returns all movies in the database.
i want to be able to return the ONLY the movie the user has clicked on, seeing that this view already returns a particular movie clicked on by a user. How can i do this?
My current method gives me an exception error 'unicode' object has no attribute 'get'
In my forms.py i have this
class MoviePaymentsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MoviePaymentsForm, self).__init__(*args, **kwargs)
movie = forms.ModelChoiceField(queryset=Movie.objects.get(slug=args[0]))
and in my views.py i have this
class SpecificMovieTemplateView(TemplateView):
model = Movie
template_name = 'movie.html'
def get_context_data(self, *args, **kwargs):
context = super(SpecificMovieTemplateView, self).get_context_data(**kwargs)
context['movie'] = Movie.objects.get(slug=kwargs['movieslug'])
print 'Movie ID is ==> ' + str(context['movie'].id)
context['form_movie'] = MoviePaymentsForm(kwargs['movieslug'])
return context
in my models.py i have this
class MoviePayments(TimeStampedModel):
uuid_placement = shortuuid.encode(uuid.uuid4())
short_uuid = uuid_placement[:8]
reference_id = models.CharField(max_length=8, blank=True, unique=True,
default="%s" % str(short_uuid))
movie = models.ForeignKey(Movie)
ticket = models.ForeignKey(Ticket)
quantity = models.IntegerField()
date = models.ForeignKey(MovieShowDate)
time = models.ForeignKey(MovieShowTimes)
paid_for = models.BooleanField(default=False, blank=False)
mm_transaction_id = models.CharField(max_length=100, blank=True)
I finally figured it out. Like Bogdan pointed out above, i needed to pass the slug field as an argument in the init method, and use filter on the queryset to return that particular movie like so
class MoviePaymentsForm(forms.ModelForm):
def __init__(self, slug, *args, **kwargs):
super(MoviePaymentsForm, self).__init__(*args, **kwargs)
self.fields['movie'].queryset = Movie.objects.filter(slug=slug)
The problem is you are passing the movie_slug as first parameter to the form:
context['form_movie'] = MoviePaymentsForm(kwargs['movieslug']) and first parameter to the form is the data dictionary. Modify the form like this:
class MoviePaymentsForm(forms.ModelForm):
def __init__(self, slug=None, *args, **kwargs):
super(MoviePaymentsForm, self).__init__(*args, **kwargs)
movie = forms.ModelChoiceField(queryset=Movie.objects.get(slug=slug))
Or remove the argument from the args list like: slug = args.pop(0)