I am trying to understand how model methods work.
Taking the following example: source here
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
When is baby_boomer_status method called?
I tried replacing return with print but the method was never called.
How does this work? Sorry for the noob question.
If you have a Person object
my_person = Person.objects.first()
You can call the model method:
my_person.baby_boomer_status()
Since this return a string, you can print its result
print my_person.baby_boomer_status()
Related
I have a Django model that has a user inputted field called reserve_time. When a user enters and creates (or edits) a instance of the class I want a function to run and check if the time inputted is during dinner or lunch. I have been trying to use post_save to do this but it hasn't been working, the print statement would say if it was dinner or not but the instance of the class would not be changed at all. Also the current way I'm doing it Reservation.objects.last() only works if right if I create a new instance, not if I edit an old one.
models.py:
from django.db import models
import datetime
import operator
from django.utils.timezone import now, localtime
from django.db.models.signals import post_save
# Create your models here.
class Reservation(models.Model):
shift = ''
name = models.CharField(max_length=40)
member_id = models.CharField(max_length=10, default="")
guest_num = models.IntegerField()
reserve_time = models.TimeField(default=datetime.datetime.now)
notes = models.CharField(max_length=100, blank=True)
date_created = models.DateTimeField(default=datetime.datetime.now, blank=True)
def __str__(self):
return f"{self.name} at {self.reserve_time.strftime('%I:%M %p')} ({self.shift}) {self.date_created.strftime('%m/%d/%Y')}"
def model_created_or_updated(sender, **kwargs):
the_instance = kwargs['instance']
reservation = Reservation.objects.last()
if reservation.reserve_time < datetime.time(17, 10, 0):
reservation.shift = 'lunch'
print('lunch')
else:
reservation.shift = 'dinner'
print('dinner')
print('model created/updated')
post_save.connect(model_created_or_updated, sender=Reservation)
Is there a substitute for Reservation.objects.last() that just calls the specific instance I am changing or adding? and how come my changing the variable shift in the if/else statement doesn't actually change it? My end goal is for this to work automatically anytime a new instance is created/edited.
what for do you use reservation = Reservation.objects.last()?
you have an instance that sends signal to you
the_instance = kwargs['instance']
if the_instance.reserve_time < datetime.time(17, 10, 0):
the_instance.shift = 'lunch'
print('lunch')
else:
the_instance.shift = 'dinner'
print('dinner')
print('model created/updated')
you can do it other way - override save method of your model
class Reservation(models.Model):
...
def save(self, *args, **kwargs):
if self.reserve_time < datetime.time(17, 10, 0):
self.shift = 'lunch'
print('lunch')
else:
self.shift = 'dinner'
print('dinner')
print('model created/updated')
return super().save(*args, **kwargs)
In the Django documentation, they recommend writing business logic in Model.
How do the View layer or queryset access the methods in Model ?
As per example in documentation (https://docs.djangoproject.com/en/3.0/topics/db/models/)
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
How do view layer access the baby_boomer_status ?
I have a little experienced in Django development but I used to write logics in View itself.
This can be done by simply calling function. For example,
>>> from .models import Person
>>> person = Person.objects.get(id=1) # Remember getting the person object
>>> person.baby_boomer_status()
You have to first get person object otherwise, it will return function itself, e.g
>>> from .models import Person
>>> person.baby_boomer_status()
>>> <function AppName.models.Person.baby_boomer_status(self)>
You can just call the method on the person instance:
person = Person.objects.get(id=1)
print(person.baby_boomer_status())
You can iterate over QuerySet and call the model method as
for person in Person.objects.all():
print(person.baby_boomer_status())
If you have a single object, just call the method directly as,
print(Person.objects.get(pk=123).baby_boomer_status())
So the title states my objective. Here's the code I wrote to achieve it:
#for each cycle in instructional cycles:
for cycle in Instructional_Cycle.objects.all():
#for each standard in the currently selected cycle:
for standard in cycle.standards.all():
#generate random percentage of correct grade-level students to pass that standard, say between 20 and 90%
percent_pass = randint(20,90)
#calculate number of total students for that grade-level that percentage represents (percentage times total number of students in that grade)
total_students_passing = (percent_pass/100) * Student.objects.filter(grade_level = standard.grade_level).count()
already_selected = []
#while length of list of already selected students < total needed
while len(already_selected) < total_students_passing:
#select a random student out of that grade
count = Student.objects.filter(grade_level=standard.grade_level).count()
random_student = Student.objects.all()[randint(0, count - 1)] #single random object
#if that student isn't in list of already selected students
if not random_student.id in already_selected:
#create a passing progress report with the end date of that instructional cycle
Progress_Report.objects.create(date=cycle.date_finished, student=random_student, standard_mastered=standard, mastery_status=True)
#add that student to list of already selected students
already_selected.append(random_student.id)
This ends with the following error:
django.db.utils.IntegrityError: UNIQUE constraint failed:
student_progress_progress_report.standard_mastered_id
The progress_report table that I'm trying to fill is empty. I can add records to it using the admin interface. So I'm not sure where to look to fix my issue, because I'm not really sure what the problem is. Thanks for looking and any tips that you can provide. -- GH
Here are the models:
from django.db import models
from django.urls import reverse
gradeLevels = ((6,6), (7,7),(8,8),(9,9), (10,10), (11,11), (12,12))
subjects = (('Literacy','Literacy'), ('Math','Math'),
('Science','Science'), ('Social Studies','Social Studies'))
class Student(models.Model):
student_id = models.CharField(max_length=8, unique=True)
last_name = models.CharField(max_length=100)
first_name = models.CharField(max_length=100)
grade_level = models.IntegerField(choices=gradeLevels)
active_status = models.BooleanField(default=True)
class Meta:
ordering = ['grade_level', 'last_name']
def __str__(self):
#Return a string representation of the model.
return self.student_id + ' ' + self.last_name + ', ' + self.first_name
def student_name(self):
return self.last_name + ', ' + self.first_name
def get_absolute_url(self):
return reverse('student_progress:student_detail', args=[str(self.id)])
class Standard(models.Model):
subject = models.CharField(max_length=14, choices=subjects)
grade_level = models.IntegerField(choices=gradeLevels)
descriptor = models.CharField(max_length=15)
description = models.TextField()
essential_status = models.BooleanField(default=False)
class Meta:
ordering = ["subject", "grade_level", "descriptor"]
def __str__(self):
return self.descriptor + ': ' + self.description[:100]
def get_absolute_url(self):
return reverse('student_progress:standard_detail', args=[str(self.id)])
class Milestone (models.Model):
step_number = models.IntegerField()
statement = models.CharField(max_length=250, default="I can ...")
standard = models.ForeignKey(Standard, on_delete=models.CASCADE,
related_name='milestones', null=True, blank=True)
def __str__(self):
return str(self.step_number) + ': ' + self.statement[:50]
class Progress_Report(models.Model):
date = models.DateField(null=True)
student = models.OneToOneField(Student, on_delete=models.CASCADE)
standard_mastered = models.OneToOneField(Standard,
on_delete=models.CASCADE)
mastery_status = models.BooleanField(default=True)
class Meta:
ordering = ["date", "student"]
def __str__(self):
return self.date
class Instructional_Cycle(models.Model):
date_started = models.DateField(blank=False)
date_finished = models.DateField(blank=False)
standards = models.ManyToManyField(Standard, related_name="standards")
class Meta:
ordering = ['date_started']
def __str__(self):
return str(self.date_started) + ' to ' + str(self.date_finished)
def get_absolute_url(self):
return reverse('student_progress:cycle_detail', args=[str(self.id)])
you've told the database you want to maintain a unique constraint! the data you're trying to insert would violate that constraint and therefore the transaction is failing.
Django provides various helpers for this. For example Progress_Report.objects.update_or_create(…) might help. For more info, see:
https://docs.djangoproject.com/en/2.1/ref/models/querysets/#update-or-create
The exact invocation would depend on the constraints you're wanting to enforce.
I stumbled on the answer accidentally: I changed the field types of Progress_Report.student and Progress_Report.standard_mastered from OnetoOneField to ForeignKey. Now the python code runs perfectly and populates the database exactly as intended without error.
I have no idea why this solved the problem. I wish I understood better what the problem is, but I'm thankful it's fixed.
I am working with a pre-existing database called Employee. I have three separate fields i'd like to combine into a single field, but I can't add an additional field to the pre-exisiting database.
I know the proper way to combine multiple fields into one field using python is
'%s - %s %s' % (self.username, self.firstname, self.lastname)
However, I can't call self outside the model, or at least i'm not sure where I would call self.
My end goal is to have a select box with the combined field a user can search either first, last, or account name. My current model looks like the following:
class Employee(models.Model):
staff_id = models.IntegerField(db_column = 'Employee_ID')
status_id = models.IntegerField(db_column = 'StatusID')
username = models.CharField(db_column = 'SamAccountName',primary_key = True, max_length = 31)
lastname = models.CharField(db_column = 'Surname', max_length = 63)
firstname = models.CharField(db_column = 'GivenName', max_length = 63)
title = models.CharField(db_column = 'Title', max_length = 127)
class Meta:
managed = False
db_table = '[Employee]'
I tried to add to my model, but when I call full_username it says the field doesn't exists, which is true because there isn't a field in the database. We aren't allowed to add a new field to the database.
def get_full_name(self):
full_username = '%s - %s %s' % (self.username, self.firstname, self.lastname)
return full_username.split()
Ideally i'd want my view to look something like this (i know it wont' work as is, i'd replace that with 'full_username):
activeuserlist = Employee.objects.filter(staff_id = '1').values_list('%s - %s %s' % (Employee.username, Employee.firstname, Employee.lastname), flat = True)
How would I get the full name added to my view, what am I missing with my logic or where would be the correct place to put it?
You can give this a try:
from django.db.models.functions import Concat
from django.db.models import F, Value
employees = Employee.objects.annotate(full_username=Concat(F('username'), Value(' - '), F('firstname'), Value(' '), F('lastname')))\
.filter(staff_id='1', full_username__icontains='hello')
The icontains bits is just a demo, with this query you can filter the result based on the combined name as well.
If you have to use this everywhere, then I recommend you create your own queryset/manager in your model then put this annotation into the default queryset. After that you can use your full_username filter any where you want without having to add the annotation first.
First, try formatting the string in a good way
full_username = '{} - {} {}'.format(self.username, self.firstname, self.lastname)
Second you can the method get_full_name() in model and call it from the model object.
employee = Employee.objects.get(id=1)
full_name = employee.get_full_name()
That should work. :)
Try using a property:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
#property
def full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
If you don't need the functionality of a QuerySet, try this
activeuserlist = [
'{} - {} {}'.format(user.username, user.firstname, user.lastname)
for user in Employee.objects.filter(staff_id = '1')
]
If you do need a QuerySet, I think it's not possible on python-level, only on SQL-level. See this thread on annotations.
edit: I completely rewrote the question as the original one didn't clearly explain my question
I want to run a function which is specific to each particular model instance.
Ideally I want something like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.FunctionField() #stores a function specific to this instance
x = MyModel(data='originalx', perform_unique_action=func_for_x)
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModel(data='originaly', perform_unique_action=func_for_y)
y.perform_unique_action() #will do whatever is specified for instance y
However there is no datatype FunctionField. Normally this would be solvable with inheritance, and creating subclasses of MyModel, maybe like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class MyModelX(MyModel):
perform_unique_action = function_X
class MyModelY(MyModel):
perform_unique_action = function_Y
x = MyModelX(data='originalx')
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModelY(data='originaly')
y.perform_unique_action() #will do whatever is specified for instance y
Unfortunately, I don't think I can use inheritance because I am trying to access the function this way:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel = SecondModel.objects.get(other_data=3)
secondmodel.mymodel.perform_unique_action()
The problem seems to be that I don't know what type the foreign key is going to be in SecondModel if I override the perform_unique_action in subclasses.
Can I access MyModel from SecondModel as a foreign key and still have a unique function for each instance of MyModel?
This works for me. I haven't tested it, but you should be able to create another class and override their methods and it'll work. Check the class Meta line, it'll treat it as an abstract class. Here's an example of my actual classes that I'm working on right now.
EDIT: Added VoteComment class and tested it. It works as expected!
class Vote(models.Model):
VOTE_ENUM = (
(VoteEnum.DOWN_VOTE, VoteEnum.toString(VoteEnum.DOWN_VOTE)),
(VoteEnum.NONE, VoteEnum.toString(VoteEnum.NONE)),
(VoteEnum.UP_VOTE, VoteEnum.toString(VoteEnum.UP_VOTE)),
)
question = models.ForeignKey(Question, null=False, editable=False, blank=False)
voter = models.ForeignKey(User, blank=False, null=False, editable=False)
vote_type = models.SmallIntegerField(default=0, null=False, blank=False, choices=VOTE_ENUM)
class Meta:
abstract = True
def is_upvote(self):
return self.vote_type > 0
def is_downvote(self):
return self.vote_type < 0
class VoteAnswer(Vote):
answer = models.ForeignKey(Answer, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "answer"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, answer.text[:32])
def is_upvote(self):
return "FOO! "+str(super(VoteAnswer, self).is_upvote())
class VoteComment(Vote):
comment = models.ForeignKey(Comment, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "comment"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, comment.text[:32])
def is_upvote(self):
return "BAR!"
I came up with two ways of having a specific function defined for each object. One was using marshal to create bytecode which can be stored in the database (not a good way), and the other was by storing a reference to the function to be run, as suggested by Randall. Here is my solution using a stored reference:
class MyModel(models.Model):
data = models.CharField(max_length=100)
action_module = models.CharField(max_length=100)
action_function = models.CharField(max_length=100)
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel_obj = SecondModel.objects.get(other_data=3)
#The goal is to run a function specific to the instance
#of MyModel referred to in secondmodel_obj
module_name = secondmodel_obj.mymodel.action_module
func_name = secondmodel_obj.mymodel.action_function
module = __import__(module_name)
func = vars(module)[func_name]
func()
Thanks to everyone who replied, I couldn't have got to this answer if it weren't for your help.
You could achive some similar behavior overriding the save method. And providing special callbacks to your instances.
Something like:
def default_function(instance):
#do something with the model instance
class ParentModel(model.Model):
data = models.CharField()
callback_function = default_function
def save(self, *args, **kwargs):
if hasattr(self, 'callback_function'):
self.callback_function(self)
super(ParentModel, self).save(*args, **kwargs)
class ChildModel():
different_data = models.CharField()
callback_function = other_fun_specific_to_this_model
instance = ChildModel()
#Specific function to this particular instance
instance.callback_function = lambda inst: print inst.different_data
instance.save()
You can write endpoints on your server and limit their access to just your self. Then store in each model instance corresponding url. For example:
views.py
def funx_x(request):
pass
def func_y(request):
pass
models.py:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.URLField()
and then:
x = MyModel(data='originalx', perform_unique_action='http://localhost/funx_x')
requests.post(x.perform_unique_action)
i dont know whether i understand u correct or not. but you can check out this example here.
Example:
A string representing an attribute on the model. This behaves almost the same as the callable, but self in this context is the model instance. Here's a full model example:
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
def decade_born_in(self):
return self.birthday.strftime('%Y')[:3] + "0's"
decade_born_in.short_description = 'Birth decade'
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'decade_born_in')