Django Model method test - django

I have model like this:
class Users(models.Model):
pin = models.CharField(max_length=16, unique=True)
name = models.CharField(max_length=64)
surname = models.CharField(max_length=64, blank=True, null=True)
patronymic = models.CharField(max_length=64, blank=True, null=True)
def get_full_name(self):
name = self.name
surname = self.surname
if self.surname is None:
surname = ''
if self.name is None:
name = ''
return str(name) + ' ' + str(surname)
I want to write a test case for get_full_name method.
Currently I wrote something like this:
class TestUsersModel(TestCase):
def setUp(self):
self.data1 = Users.objects.create(name='Bob', pin='1')
self.data2 = Users.objects.create(surname='Alex', pin='2')
def test_users_model_entry(self):
data = self.data1
self.assertTrue(isinstance(data, Users))
def test_users_full_name_only_name(self):
data = self.data1.get_full_name()
self.assertEquals(data,'Bob ')
def test_users_full_name_only_surname(self):
data = self.data2.get_full_name()
self.assertEquals(data,' Alex')
but when I check with coverage it says me that I have to write test case for this:
What I am missing here?

#sevdimali - Firstly, it is a bad idea to name a field as name and not first_name when you have the other field as surname. Also, if you decide to use first_name then the other field should be last_name. Consistency matters.
class Users(models.Model):
pin = models.CharField(max_length=16, unique=True)
name = models.CharField(max_length=64,null=True)#or else you cannot test #object creation without name
surname = models.CharField(max_length=64, blank=True, null=True)
patronymic = models.CharField(max_length=64, blank=True, null=True)
Now, to test your get_full_name(). How about we do something like this?
def setUp(self):
self.user = Users.objects.create(name='test', surname='world',pin='1234')
self.user_without_surname = Users.objects.create(name='test',pin='12345')
self.user_without_name=Users.objects.create(surname='world',pin='123456')
def test_get_full_name_with_valid_name_and_surname(self):
expected = str(self.user.name) + ' ' + str(self.user.surname)
actual = 'test world'
self.assertEquals(expected,actual)
def test_get_full_name_with_empty_surname(self):
expected = str(self.user.name) + ' ' +''
actual = 'test '
self.assertEquals(expected,actual)
def test_get_full_name_with_empty_name(self):
expected = '' + ' ' +str(self.user.surname)
actual = ' world'
self.assertEquals(expected,actual)
Key things to refactor-
IMO, the Model name should not be plural
Name doesn't make sense. Be explicit. first_name,middle_name,last_name if needed
Think about null columns and the consequences they might have. You don't want to have a user without even the first_name, last_name, email, and other essential fields.

Related

Django models - how to assign as ForeignKey

My lab has a models.py as below:
class Book(models.Model):
isbn = models.CharField(max_length=10, unique=True)
name = models.CharField(max_length=100)
published_year = models.IntegerField()
total_qty = models.IntegerField()
current_qty = models.IntegerField()
max_duration = models.IntegerField()
author = models.ForeignKey(Author, on_delete=models.PROTECT)
category = models.ForeignKey(Category, on_delete=models.PROTECT)
def __str__(self):
return self.name
class BookCopy(models.Model):
class Status:
AVAILABLE = 1
BORROW =2
LOST = 3
barcode = models.CharField(max_length=30, unique=True)
buy_date = models.DateField(null=True, blank=True)
status = models.IntegerField()
book = models.ForeignKey(Book, on_delete=models.PROTECT)
def __str__(self):
return self.barcode
class User(models.Model):
username = models.CharField(max_length=30, unique=True)
fullname = models.CharField(max_length=100, null=True)
phone = models.CharField(max_length=10, null=True)
def __str__(self):
return self.fullname
class BookBorrow(models.Model):
class Status:
BORROWING = 1
RETURNED = 2
borrow_date = models.DateField()
deadline = models.DateField()
return_date = models.DateField(null=True)
status = models.IntegerField()
book_copy = models.ForeignKey(BookCopy, on_delete=models.PROTECT)
book_name = models.ForeignKey(Book, on_delete=models.PROTECT)
user = models.ForeignKey(User, on_delete=models.PROTECT)
And i wrote the api for borrow_book function like below:
#csrf_exempt
def muon_sach(request):
body = request.POST
username = body.get('username')
barcode = body.get('barcode')
user = User.objects.filter(username=username).first()
bookcopy = BookCopy.objects.filter(barcode = barcode).first()
if not user:
return HttpResponse(json.dumps({
'error':"Nguoi dung khong ton tai"
}))
if not bookcopy:
return HttpResponse(json.dumps({
'error':"ma sach khong ton tai"
}))
book_borrow = BookBorrow()
# resp = []
book_borrow.user = user
book_borrow.book_copy = bookcopy
book_borrow.borrow_date = datetime.now()
book_borrow.deadline = datetime.now() + timedelta(days=bookcopy.book.max_duration)
book_borrow.status = BookBorrow.Status.BORROWING
book_borrow.book_name = bookcopy.book.name
book_borrow.save()
bookcopy.status = BookCopy.Status.BORROW
bookcopy.save()
bookcopy.book.current_qty -=1
bookcopy.book.save()
return HttpResponse(json.dumps({'success':True}))
however when i test with postman (give username and barcode), it gets the error
xxx "BookBorrow.book_name" must be a "Book" instance."
Could you please advise where incorrect and assist me correct it ? Appreciate for any assist
You have to do the following:
#csrf_exempt
def muon_sach(request):
# ... more code here
bookcopy = BookCopy.objects.filter(barcode = barcode).first()
book_borrow.book_name = bookcopy.book
book_borrow.save()
# ... more code here
return HttpResponse(json.dumps({'success':True}))
So in the definition of your model you can see that book_name has the following structure:
class BookBorrow(models.Model):
# ... More code here
book_name = models.ForeignKey(Book, on_delete=models.PROTECT)
user = models.ForeignKey(User, on_delete=models.PROTECT)
It is clear that BookBorrow.book_name must accept a Book instance. So when you pass in you code book_borrow.book_copy = bookcopy it is passing a BookCopy instance so that's the error.
borrow_copy.book is the appropiate.
You have specified book_name to be a Foreign Key to Book, and you try to assign to it the book.name value.
Either you need to set this field as a CharField or you need to rename the field from book_name to book and use book_borrow.book = bookcopy.book

Saving many-to-many fields from the excel file in Django

I'm trying to save the student data from an excel file. I'm reading the excel file row-wise and mapping the data to the model fields. Now the problem is that there is a foreign key and a many-to-many field which I don't know how to save. Though I figured out the foreign key part but not able to solve the second part.
Here are the files.
views.py
def fileUpload(request):
if request.method=="POST":
form= UserDataUploadView(request.POST, request.FILES)
try:
excel_file= request.FILES["excel_file"]
except MultiValueDictKeyError: # In case the user uploads nothing
return redirect('failure_page')
# Checking the extension of the file
if str(excel_file).endswith('.xls'):
data= xls_get(excel_file, column_limit=10)
elif str(excel_file).endswith('.xlsx'):
data= xlsx_get(excel_file, column_limit=10)
else:
return redirect('failure_page')
studentData= data["Sheet1"]
print("Real Data", studentData)
# reading the sheet row-wise
a_list= studentData
list_iterator= iter(a_list)
next(list_iterator)
for detail in list_iterator:
# To find out empty cells
for data in detail:
if data==" ":
print('A field is empty')
return redirect('user_upload')
print("DATA: ", detail)
user=User.objects.create(
firstName = detail[6],
lastName = detail[7],
password = detail[8],
username = detail[9],
)
# instance=user.save(commit=false)
# Student.batch.add(detail[0])
student=Student.objects.create(
user = user,
email = detail[1],
rs_id = detail[2],
dob = detail[3],
address = detail[4],
age = detail[5],
)
student.save()
return render(request, 'classroom/admin/success_page.html', {'excel_data':studentData})
# iterating over the rows and
# getting value from each cell in row
# for row in worksheet.iter_rows():
# row_data= list()
# for cell in row:
# row_data.append(str(cell.value))
# excel_data.append(row_data)
# return render(request, 'classroom/admin/excel.html', {'excel_data':excel_data})
else:
form=UserDataUploadView()
return render(request, 'classroom/admin/fill_users.html', {
'form':form,
# 'excel_data':excel_data,
})
models.py
class Subject(models.Model):
school = models.CharField(max_length=50, null=True)
name = models.CharField(max_length=30)
color = models.CharField(max_length=7, default='#007bff')
def __str__(self):
return self.name
def get_html_badge(self):
name = escape(self.name)
color = escape(self.color)
html = '<span class="badge badge-primary" style="background-color: %s">%s</span>' % (color, name)
return mark_safe(html)
class Batch(models.Model):
name = models.CharField(max_length=30, unique=True)
school = models.CharField(max_length=50)
amount_of_fees = models.IntegerField(null=True)
subjects = models.ManyToManyField(Subject)
#property
def students(self):
return self.student_set.all()
def __str__(self):
return self.name
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, default=None)
batch = models.ManyToManyField(Batch)
email = models.EmailField(null=True)
phone_number = models.CharField(max_length=10, null=True)
dob = models.DateField(blank=True, null=True, help_text="Enter in the following format : YYYY-MM-DD")
address = models.TextField(max_length=150, null=True)
age = models.IntegerField(blank=True)
image = models.ImageField(upload_to='profile_pictures', default='student_image.png', blank=True)
rs_id = models.IntegerField(blank=True,default=0)
I don't know how to put the data for batch in the excel sheet. Kindly give insight for that too.
Assuming detail[0] is the name field for the Batch model, you would do:
student_batch = Batch.objects.get(name=detail[0])
student=Student.objects.create(
user = user,
email = detail[1],
rs_id = detail[2],
dob = detail[3],
address = detail[4],
age = detail[5],
)
student.batch.add(student_batch)
student.save()
You will also need to update your Batch field on the Student model to:
batch = models.ManyToManyField(Batch, blank=True)

django __str__ returned non-string(type tuple)

Hello i'm studying django, and
i have a problem with def str on models
models.py
class Student(models.Model):
id = models.AutoField(primary_key=True)
school_year = models.CharField(max_length=4,choices=SCHOOL_YEAR_CHOICES,default='2019')
campus = models.CharField(max_length=1,choices=CAMPUS_CHOICES,default='M')
type = models.CharField(max_length=1,choices=TYPE_CHOICES,default='N')
affiliate = models.CharField(max_length=1,choices=AFFILIATE_CHOICES,default='N')
name = models.CharField(max_length=30, unique=True)
def __str__(self):
return self.name,self.campus
class Comment(models.Model):
student = models.ForeignKey(Student, related_name='comments',on_delete=models.CASCADE)
created_by = models.ForeignKey(User,related_name='comments',on_delete=models.CASCADE)
message = models.TextField(max_length=1000, unique=False)
about = models.CharField(max_length=1,choices=ABOUT_CHOICES,default='L')
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __str__(self):
return self.created_by.first_name, self.student.name
if i remove that " student = models.ForeignKey ~~" on class Comment, it'll work
i think 'student' on class comment makes a problem
anyone help? thanks
and i'm using python3 with the lastest version of django
Use this on Comment model:
def __str__(self):
return self.created_by.first_name + ' ' + self.student.name
Also on Student model:
def __str__(self):
return self.name + ' ' + self.campus
Redefine __str__:
def __str__(self):
return self.name+' '+self.campus

using "primary" field in django model

i have two models: Address and Phone. Inside each model, rests a "Default" boolean field. What I need it to do, is if I submit a True answer in a form, then all other records must be set to False for that user.
How do I accomplish this?
class Address (models.Model):
User = models.ForeignKey(User)
Primary = models.BooleanField(default=True)
Street = models.CharField(max_length=500)
City = models.CharField(max_length=50)
State = models.CharField(max_length=40)
Zip = models.CharField(max_length=20)
County = models.CharField(max_length=20)
Country = models.CharField(max_length=50, default="United States")
Latitude = models.FloatField(null=True, blank=True)
Longitude = models.FloatField(null=True, blank=True)
class Meta:
verbose_name_plural = "Addresses"
def __str__(self):
primary = 'PRIMARY Address for ' if self.Primary else 'Address for '
return primary + self.User.first_name + ' ' + self.User.last_name
def save(self, *args, **kwargs):
geolocator = Nominatim()
location = geolocator.geocode("{} {}, {}, {}".format(self.Street, self.State, self.Zip, self.Country))
self.Latitude = location.latitude
self.Longitude = location.longitude
super(Address, self).save(args, *kwargs)
class Phone (models.Model):
User = models.ForeignKey(User)
Primary = models.BooleanField(default=True)
Country_Code = models.CharField(max_length=5, default="001")
Area_Code = models.CharField(max_length=5, blank=True, null=True)
Number = models.CharField(max_length=20, blank=True, null=True)
def __str__(self):
return self.Country_Code + "-" + self.Area_Code + "-" + self.Number
You can use post_save signal or override save method. Following is a simple snippet. If you want to keep consistent, put the these queries in a transaction.
def save(self, *args, **kwargs):
geolocator = Nominatim()
location = geolocator.geocode("{} {}, {}, {}".format(self.Street, self.State, self.Zip, self.Country))
self.Latitude = location.latitude
self.Longitude = location.longitude
super(Address, self).save(args, *kwargs)
Address.objects.exclude(id=self.id).update(Primary=False)

Converting raw SQL to Django QuerySet API

Model
class Person(models.Model):
GENDER = (
('M','Male'),
('F','Female'),
)
first_name = models.CharField("First Name", max_length=100)
last_name = models.CharField("Last Name",max_length=100)
middle_name = models.CharField("Middle Name", max_length=100, blank=True)
suffix_name = models.ManyToManyField(SuffixName, verbose_name="Suffix Name",null=True, blank=True)
gender = models.CharField(max_length=1, choices=GENDER)
department = models.ManyToManyField(Department, verbose_name="Department",null=True, blank=True)
def __unicode__(self):
return (u'%s') % (self.last_name.upper() + ', ' + self.first_name + ' ' + self.middle_name)
class Department(models.Model):
department_desc = models.CharField('Department', max_length=100,unique=True)
department_acronym = models.CharField('Department Acronym', max_length=20,blank=True,help_text="Add acronym if any, not required")
location = models.CharField('Location',max_length=100,blank=True)
localnumber = models.CharField('Local Number',max_length=30,blank=True)
active = models.BooleanField('Active',default=True)
def __unicode__(self):
return self.department_desc
How can I convert this raw SQL to a Django query?
SELECT pp.first_name, pd.department_desc, pd.localnumber
FROM person_person as pp
INNER JOIN person_person_department as ppd on pp.id = ppd.person_id
INNER JOIN person_department as pd on pd.id = ppd.department_id
Use QuerySet.values or QuerySet.values_list:
Person.objects.values('first_name', 'department__department_desc', 'department__localnumber')
# -> Returns a ValuesQuerySet that yields dictionaries when iterated.
Person.objects.values_list('first_name', 'department__department_desc', 'department__localnumber')
# -> Returns a ValuesQuerySet that yields tuples when iterated.
Fetching on Person will automatically fetch the related objects, so your query is:
people = Person.objects.all()
Then, you can do the following:
for person in people:
print('Person: {0}'.format(person.first_name))
for dept in person.department_set.all():
print('Desc: {0}, Local Number: {1}'.format(dept.department_desc,
dept.localnumber))
If you want to restrict the returned values, use values_list.