Django testing doesn't recognize dirty_fields? - django

One of my models uses dirty_fields. The save method detects when the field scale_mode changes. Once this happens, it goes through all the related grade objects and changes the field score for each affected grade. The goal is if VSB is swapped with VSBPLUS, then APP is swapped with APP+.
model:
class GradeBookSetup(DirtyFieldsMixin, models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=CASCADE)
VSB = 'VSB'
VSBPLUS = 'VSBPLUS'
SCALEMODE = [
('VSB', 'BEG DEV APP EXT'),
('VSBPLUS', 'BEG DEV APP APP+ EXT'),
]
scale_mode = models.CharField(
max_length=7, choices=SCALEMODE, blank=True, default=VSB)
def save(self, *args, **kwargs):
super(GradeBookSetup, self).save(*args, **kwargs)
if self.is_dirty():
dirty_fields = self.get_dirty_fields()
if 'scale_mode' in dirty_fields:
if self.scale_mode == "VSB":
n = 0
objs = Grade.objects.filter(user=self.user)
for grade in objs:
if grade.score == "APP+":
objs[n].score = "APP"
n = n + 1
Grade.objects.bulk_update(objs, ['score'])
elif self.scale_mode == "VSBPLUS":
objs = Grade.objects.filter(user=self.user)
for grade in objs:
if grade.score == "APP":
objs[n].score = "APP+"
Grade.objects.bulk_update(objs, ['score'])
class Grade(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
INCOMPLETE = 'I'
BEGINNING = 'BEG'
DEVELOPING = 'DEV'
APPLYING = 'APP'
APPLYINGPLUS = 'APP+'
EXTENDING = 'EXT'
score = models.CharField(
max_length=4, blank=True, default=INCOMPLETE)
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
assessment = models.ForeignKey(
Assessment, on_delete=models.CASCADE, null=True, blank=True)
objective = models.ForeignKey(
Objective, on_delete=models.CASCADE, blank=True)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
cblock = models.ForeignKey(Classroom, on_delete=models.CASCADE, default=1)
I am writing tests for this model. Each of the test below passes except for the last test, test_scale_mode_change.
class GradeBookSetupTest(TestCase):
#classmethod
def setUpTestData(cls):
cls.user = get_user_model().objects.create_user(
username='tester',
email='tester#email.com',
password='tester123'
)
cls.gbsetup = GradeBookSetup(
user=cls.user,
scale_mode="VSBPLUS",
)
cls.gbsetup.save()
cls.student = Student.objects.create(
user=student_user,
student_first="Jim",
student_last="Smith",
nickname="Jim S",
fullname="Jim Smith",
student_number=992342,
email="992342#learn.vsb.bc.ca"
)
cls.course = Course.objects.create(
user=cls.user,
course_name="Math8",
grade_level='ELEM',
)
cls.classroom = Classroom(
user=cls.user,
classroom_name='Block 1',
course=cls.course,
)
cls.classroom.students.add(cls.student)
cls.classroom.save()
cls.objective = Objective.objects.create(
user=cls.user,
objective_name='factor.1',
mode='LA',
course=cls.course,
description="Test objective",
)
cls.assessment = Assessment(
user=cls.user,
assessment_name="Quiz1",
course=cls.course,
)
cls.assessment.objectives.add(cls.objective)
cls.assessment.save()
cls.grade = Grade.objects.create(
user=cls.user,
score='APP+',
student=cls.student,
cblock=cls.classroom,
assessment=cls.assessment,
objective=cls.objective
)
def test_gradebooksetup_creation(self):
self.assertEqual(self.gbsetup.scale_mode, "VSBPLUS")
def test_grade_creation(self):
self.assertEqual(self.grade.score, 'APP+')
self.assertNotEqual(self.grade.score, 'DEV')
def test_scale_mode_change(self):
self.gbsetup.scale_mode = 'VSB'
self.gbsetup.save()
print(self.gbsetup.scale_mode)
self.assertEqual(self.grade.score, 'APP')
What I expect to happen is that `self.gbsetup.scale_mode = 'VSB' should trigger the dirty_field in the save method. This should then change the APP+ score to APP. The print statement confirms that gbsetup == VSB. In practice, my app does perform this function correctly.
What happens though is:
self.assertEqual(self.grade.score, 'APP')
AssertionError: 'APP+' != 'APP'
I didn't show all the related models for Grade and GradeBookSetup, I don't think they are relevant to this problem. I can add these if required.

You might need to regrab the grade from the database as the change is made in the DB but the reference instance is still the one created in the setup.
def test_scale_mode_change(self):
self.gbsetup.scale_mode = 'VSB'
self.gbsetup.save()
print(self.gbsetup.scale_mode)
#we have made indirect changes to grade via the gbsetup.save() function that we need to check
self.grade.refresh_from_db()
self.assertEqual(self.grade.score, 'APP')

Related

do some things same time in the Django unit test

How can I test if two users can reserve the same car simultaneously?
def test_if_two_users_can_reserve_the_same_car_simultaneously(self):
with patch.object(
timezone,
"now",
return_value=make_aware(
datetime.datetime.combine(
datetime.date.today() + datetime.timedelta(days=1), datetime.time(10, 30, 0)
),
timezone=pytz.timezone("UTC"),
),
):
self.client.login(username=self.user.username, password=self.PASSWORD)
url = reverse("booking-list")
data = {
"book_for": datetime.datetime.combine(
datetime.date.today() + datetime.timedelta(days=1), datetime.time(11, 30, 0)
),
}
response = self.client.post(url, data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
with patch.object(
timezone,
"now",
return_value=make_aware(
datetime.datetime.combine(
datetime.date.today() + datetime.timedelta(days=1), datetime.time(10, 30, 0)
),
timezone=pytz.timezone("UTC"),
),
):
self.client.login(
username=self.another_user.username, password=self.PASSWORD
)
url = reverse("booking-list")
data = {
"book_for": datetime.datetime.combine(
datetime.date.today() + datetime.timedelta(days=1), datetime.time(11, 30, 0)
),
}
response = self.client.post(url, data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
This is how I wrote it, but in the unit test, it runs line by line. So first one will create then move to second one but I want them both run at same time. (Please answer with an example)
This is its model:
class Booking(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
user_location = models.PointField(help_text="user current location")
user_address = models.TextField(help_text="user current address")
start_location = models.PointField(
blank=True,
help_text="location where the user pick up the car from",
)
start_address = models.TextField(
blank=True,
default="",
help_text="address where the user pick up the car from",
)
end_location = models.PointField(
null=True,
blank=True,
help_text="desitination location that a user wants to drive to or driven by the driver",
)
end_address = models.TextField(
blank=True,
default="",
help_text="desitination address that a user wants to drive to or driven by the driver",
)
vehicle = models.ForeignKey(
Vehicle,
on_delete=models.PROTECT,
)
book_for = models.DateTimeField()
drop_off_datetime = models.DateTimeField(
null=True,
blank=True,
help_text="the drop off time for booking",
)
status = models.CharField(
max_length=25,
choices=BookingStatus.choices,
default=BookingStatus.OPEN,
)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
As you can see I used UUIDField.
def perform_create(self, serializer):
serializer.save(user=self.request.user)
constraints:
class Meta:
constraints = [
UniqueConstraint(
fields=["user"],
condition=Q(status=BookingStatus.ACTIVE),
name="unique_active_booking",
)
]
ordering = (
"-book_for",
"start_address",
)
enter code here

Saving data to Django database with Views

I need help with one thing in my django project.
I have class games_renderer in my views.py which is connected to the path in URLs. I need this class to take three values and then store them in the SQL database in Django in the created UserGameScore model. I tried to do this using model.object.score but the application reports an error. I think the problem is that the application does not know which line to assign the value to, but I do not know where I can determine this value, using the value player_id. The Game_id value is used to specify in which column the value is to be written.
views.py
def games_renderer(request, game_id, player_id, score):
if game_id == 1:
UserGameScore.objects.create(game1_score = score)
elif game_id ==2:
UserGameScore.objects.create(game2_score = score)
elif game_id ==3:
UserGameScore.objects.create(game3_score = score)
else:
return render(request, 'result.html',{'game_id' : game_id, 'player_id' : player_id, "score" : score} )
models.py
class UserGameScore(models.Model):
user_rec = models.ForeignKey(User, on_delete=models.CASCADE)
game1_score = models.IntegerField(null=True, blank=True)
game2_score = models.IntegerField(null=True, blank=True)
game3_score = models.IntegerField(null=True, blank=True)
You can try this.
def games_renderer(request, game_id, player_id, score):
game_score = UserGameScore()
if game_id === 1:
game_score.game1_score = score
if game_id === 2:
game_score.game2_score = score
if game_id === 3:
game_score.game3_score = score
game_score.save()
You can also change your model to simplify your view.
Here is a small suggestion.
class UserGameScore(models.Model):
GAME_CHOICES = [
(ONE, '1'),
(TWO, '2'),
(THREE, '3'),
]
user_rec = models.ForeignKey(User, on_delete=models.CASCADE)
score = models.IntegerField(null=True, blank=True)
game_type = models.CharField(
max_length=2,
choices=GAME_CHOICES,
default=ONE,
)
Your view should look similar to this:
def games_renderer(request, game_id, player_id, score):
game_score = UserGameScore(user_rec=player_id, score=score, game_type=game_id )

Django ORM Count Without Duplicate Column

I have a table called Reading History. I want to find the average number of reads in this table. I wrote the following method for this, but I can't get the right result. Records are made in the table once (student-book records with the same values). When the same record comes, the counter value is increased by one.
For example, suppose that two different students read two different books. I expect total reads / 2 but the result I get is total reads / 4 because there are 4 rows in the table. How can I calculate this? For example, if a student reads 4 different books once, the average will be 1, but the average should be 4.
I tried to use distinct and values but I couldn't get the result I wanted. Maybe I didn't manage to use it correctly. Also I tried to use Avg. When avg didn't give the correct result, I tried to break it down and get the values myself. Normally, Avg was written in Sum.
Serializer
class ClassReadingHistoryReportSerializer(ModelSerializer):
instructor = InstructorForClassSerializer(source="instructor.user")
students = StudenListReadingHistoryReportSerializer(many=True,
source="student_list_class")
avg_read_book = serializers.SerializerMethodField()
class Meta:
model = Class
exclude = [
"created_at",
"updated_at",
"school",
]
def get_avg_read_book(self, obj):
sum_read_book = Class.objects.filter(id = obj.id).aggregate(sum_read=Sum('student_list_class__child__child_reading_history__counter'))
count_child_record = Class.objects.filter(id = obj.id).aggregate(count_child=Count('student_list_class__child__child_reading_history'))
return None
Models
class ChildProfile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True,
verbose_name=AccountStrings.ChildProfileStrings.user_verbose_name,
related_name="user_child")
city = models.ForeignKey(
"country.City",
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=AccountStrings.ChildProfileStrings.city_verbose_name,
related_name="city_child_profiles")
hobbies = models.CharField(
max_length=500,
null=True,
blank=True,
verbose_name=AccountStrings.ChildProfileStrings.hobbies_verbose_name)
class Meta:
verbose_name = AccountStrings.ChildProfileStrings.meta_verbose_name
verbose_name_plural = AccountStrings.ChildProfileStrings.meta_verbose_name_plural
#property
def get_full_name(self):
return f"{self.user.first_name} {self.user.last_name}"
def __str__(self):
return self.get_full_name
def clean(self) -> None:
"""
This method will check if the user type is a child during creation.
"""
if self.user.user_type != 2:
raise ValidationError(AccountStrings.ChildProfileStrings.user_type_error)
class School(AbstractSchoolBaseModel):
city = models.ForeignKey(
"country.City",
on_delete=models.DO_NOTHING,
related_name="city_schools",
verbose_name=SchoolStrings.SchoolStrings.city_verbose_name)
name = models.CharField(
max_length=250,
verbose_name=SchoolStrings.SchoolStrings.name_verbose_name)
address = models.CharField(
max_length=250,
verbose_name=SchoolStrings.SchoolStrings.address_verbose_name)
website = models.CharField(
max_length=250,
verbose_name=SchoolStrings.SchoolStrings.website_verbose_name)
class Meta:
verbose_name = SchoolStrings.SchoolStrings.meta_verbose_name
verbose_name_plural = SchoolStrings.SchoolStrings.meta_verbose_name_plural
def __str__(self):
return self.name
class Class(AbstractSchoolBaseModel):
school = models.ForeignKey(
"school.School",
on_delete=models.CASCADE,
related_name="classes_school",
verbose_name=SchoolStrings.ClassStrings.school_verbose_name)
instructor = models.ForeignKey(
"account.InstructorProfile",
on_delete=models.CASCADE,
related_name="instructors_school",
verbose_name=SchoolStrings.ClassStrings.instructor_verbose_name)
name = models.CharField(
max_length=50,
verbose_name=SchoolStrings.ClassStrings.name_verbose_name)
grade = models.IntegerField(
verbose_name=SchoolStrings.ClassStrings.grade_verbose_name)
class Meta:
verbose_name = SchoolStrings.ClassStrings.meta_verbose_name
verbose_name_plural = SchoolStrings.ClassStrings.meta_verbose_name_plural
ordering = ["name", "grade"]
def __str__(self):
return f"{self.school.name} - {self.name} - Grade: {self.grade}"
def clean(self) -> None:
"""
This method checks whether the teacher trying to be assigned to the class is working in that school.
"""
if self.instructor.school != self.school:
raise ValidationError(SchoolStrings.ClassStrings.instructor_not_working_at_this_school_error)
class StudentList(AbstractSchoolBaseModel):
school_class = models.ForeignKey(
"school.Class",
on_delete=models.CASCADE,
related_name="student_list_class",
verbose_name=SchoolStrings.StudentListStrings.school_class_verbose_name
)
child = models.ForeignKey(
"account.ChildProfile",
on_delete=models.CASCADE,
related_name="student_list_children",
verbose_name=SchoolStrings.StudentListStrings.child_verbose_name)
class Meta:
verbose_name = SchoolStrings.StudentListStrings.meta_verbose_name
verbose_name_plural = SchoolStrings.StudentListStrings.meta_verbose_name_plural
def __str__(self):
return f"{self.school_class.name} : {self.child.user.first_name} {self.child.user.last_name}"
def clean(self) -> None:
result = StudentList.objects.filter(school_class = self.school_class, child = self.child)
if not self.pk and result.exists():
raise ValidationError(SchoolStrings.StudentListStrings.child_already_added_to_this_class_error)
class ReadingHistory(AbstractBookBaseModel):
IS_FINISHED = ((False,
BookStrings.ReadingHistoryStrings.is_finished_false),
(True, BookStrings.ReadingHistoryStrings.is_finished_true))
book = models.ForeignKey(
"book.Book",
on_delete=models.CASCADE,
related_name="book_reading_history",
verbose_name=BookStrings.ReadingHistoryStrings.book_verbose_name)
child = models.ForeignKey(
"account.ChildProfile",
on_delete=models.CASCADE,
related_name="child_reading_history",
verbose_name=BookStrings.ReadingHistoryStrings.child_verbose_name)
is_finished = models.BooleanField(
choices=IS_FINISHED,
verbose_name=BookStrings.ReadingHistoryStrings.is_finished_verbose_name
)
counter = models.PositiveIntegerField(
verbose_name=BookStrings.ReadingHistoryStrings.counter_verbose_name,
default=0)
class Meta:
verbose_name = BookStrings.ReadingHistoryStrings.meta_verbose_name
verbose_name_plural = BookStrings.ReadingHistoryStrings.meta_verbose_name_plural
def __str__(self):
return f"{self.child.user.first_name} {self.child.user.last_name} - \"{self.book.name}\" "
def clean(self) -> None:
result = ReadingHistory.objects.filter(child=self.child,
book=self.book)
if not self.pk and result.exists():
raise ValidationError(
BookStrings.ReadingHistoryStrings.exists_error)
I forgot to add the counter column in the BookReadingHistory table to the diagram. Diagram and Table data are shown in the images below.
Table
Diagram

Django - Update a record using a modelform (Table contains Unique_constraint)

The idea would be that the user should be able to go in and update the record using the same form I have provided. I included a unique constraint because the idea was that a Requisition can contain multiple Requisition_lines. For the initial phase I have hard coded sequence=1. It saved the record initially but I am now getting an Integrity error when i try to update the record using update_or_create. Any help would be appreciated! Let me know if any more information is needed.
Models.py
class Requisition(models.Model):
username = models.ForeignKey(
'users.CustomUser', on_delete=models.CASCADE, related_name='req_user')
signature = models.CharField(max_length=10, blank=True, null=True)
status = models.ForeignKey('RequisitionStatus', related_name='req_status', on_delete=models.CASCADE)
class RequisitionLine(models.Model):
parent_req = models.ForeignKey('Requisition', on_delete=models.CASCADE, related_name='par_req_line' )
sequence = models.PositiveIntegerField()
item_code = models.ForeignKey(
'items.ItemMaster', on_delete=models.CASCADE, related_name='req_item', blank=True, null=True)
description = models.CharField(max_length=50, blank=True)
extra_information = models.TextField(blank=True)
quantity = models.PositiveIntegerField(blank=True, default=0,null=True)
price = models.DecimalField(max_digits=19, decimal_places=2, blank=True, default=0.00,null=True)
purchase_order = models.CharField(max_length=9, blank=True,null=True)
po_line = models.PositiveSmallIntegerField(blank=True,null=True)
req_delivery_date = models.DateField(blank=True,null=True)
act_delivar_date = models.DateField(blank=True, null=True)
class Meta:
unique_together = ('parent_req','sequence')
Views.py
def update_requisition(request, id):
current_req = Requisition.objects.get(id=id)
if current_req.username == request.user:
data = { 'parent_req': id }
if request.method == "POST":
req_form = ReqForm(request.POST, instance = current_req)
if req_form.is_valid():
req_form_line, created = RequisitionLine.objects.update_or_create(
parent_req = current_req,
sequence = 1,
description = req_form.cleaned_data['description'],
extra_information = req_form.cleaned_data['extra_information'],
quantity = req_form.cleaned_data['quantity'],
price = req_form.cleaned_data['price'],
defaults = {'parent_req':current_req,
'sequence': 1 })
return(redirect(reverse('requisition:req_history')))
else:
try:
req_form_line = RequisitionLine.objects.get(parent_req=current_req, sequence=1)
req_form = ReqForm(initial=data, instance = req_form_line)
except RequisitionLine.DoesNotExist:
req_form = ReqForm(initial=data, instance = current_req)
return render(request, 'req/update_req.html' , {'current_req': current_req, 'req_form': req_form})
else:
return HttpResponseRedirect(reverse('requisition:req_history'))
Your usage of the update_or_create function is wrong. You misunderstand the keyword defaults (see docs). You need to put all your fields to update into this dictionary:
req_form_line, created = RequisitionLine.objects.update_or_create(
parent_req = current_req,
sequence = 1,
defaults = {
description : form.cleaned_data['description'],
extra_information : req_form.cleaned_data['extra_information'],
quantity : req_form.cleaned_data['quantity'],
price : req_form.cleaned_data['price'],
})

Getting list of many to many objects

class Team(models.Model):
team_name = models.CharField(max_length=50, null=False)
def __str__(self):
return self.team_name
class Tournament(models.Model):
types = (
('Round', 'Round'),
('Knockout', 'Knockout'),
)
teams = models.ManyToManyField(Team, related_name='tournament_teams')
tournament_name = models.CharField(max_length=50, null=False)
tournament_type = models.CharField(choices=types, max_length=40, null=False)
def __str__(self):
return self.tournament_name
class MatchRound(models.Model):
team_a_id = models.ForeignKey(Team, related_name="team_a")
team_b_id = models.ForeignKey(Team)
date = models.DateTimeField(null=True)
team_a_score = models.IntegerField(null=True)
team_b_score = models.IntegerField(null=True)
tournament_id = models.ForeignKey(Tournament, on_delete=models.CASCADE, null=True)
#receiver(post_save, sender=Tournament)
def create_match_round(sender, **kwargs):
type = kwargs.get('instance').tournament_type
if type == 'Round' and kwargs.get('created', False):
teams = kwargs.get('instance').teams.all()
schedule = create_schedule(teams)
for round in schedule:
for match in round:
team_a_id = match[0]
team_b_id = match[1]
tournament_id = kwargs.get('instance')
game = MatchRound.objects.create(team_a_id=team_a_id, team_b_id=team_b_id,
tournament_id=tournament_id)
I am trying to create a schedule for a tournament. So, I set up a trigger on MatchRound model and I am trying to get the teams of the tournament when it's created. However, the following line
teams = kwargs.get('instance').teams.all()
returns to an empty query set. I couldn't figure it out the problem.