Empty value for string in model function - django

First project newbie here - In this model I am trying to create a field that combines first and last names of two people depending if the last names are the same or not. If the last name is the same, I want it to display as "first_name1 & first_name2 last_name1". It works except that when last_name1 is empty, which will be the case a lot of the time, it displays something like "John & Jane None". I had to specify last_name1 as a string or else I got an error: must be str, not NoneType. How do I do this properly? Also what do I call this type of function in a model...is it a manager? I wasn't sure how to title this post.
class Contact(models.Model):
first_name1 = models.CharField(max_length=100, verbose_name='First Name', null=True)
last_name1 = models.CharField(max_length=100, verbose_name='Last Name', null=True, blank=True)
first_name2 = models.CharField(max_length=100, verbose_name='First Name (Second Person)', null=True, blank=True)
last_name2 = models.CharField(max_length=100, verbose_name='Last Name (Second Person)', null=True, blank=True)
def get_full_name(self):
combined_name = ''
if self.last_name1 == self.last_name2:
combined_name = self.first_name1 + ' & ' + self.first_name2 + ' ' + str(self.last_name1)
return '%s' % (combined_name)
full_name = property(get_full_name)

The reason on why you're getting an error that last_name1 must be a String, not a NoneType is due to the fact you've set null to True in your field declaration for the said field.
So what's wrong with doing that? When you are defining null=True for fields like CharField or TextField you'll end up having None. The Django convention is to use EMPTY STRING.
Here's a link that talks about how you would use blank or null on field declarations.

You can check if those values are "Truth-y" before doing the comparison check. However, you'll need to decide how to handle the other cases.
#property
def get_full_name(self):
combined_name = ''
if self.last_name1 and self.last_name2:
if self.last_name1 == self.last_name2:
combined_name = self.first_name1 + ' & ' + self.first_name2 + ' ' + str(self.last_name1)
elif self.last_name1: # Only last_name1 is set
pass
elif self.last_name2: # Only last_name2 is set
pass
else: # Both last_name1 and last_name2 are None or ''
pass
return combined_name

The way you've defined the names, any and all of them can be None, if you change them to the empty string you'll have similar problems. To illustrate, lets start by writing a unit test (substitute the empty string for None if you want):
def test_contact_full_name():
# correct.
assert Contact('Jane', None, 'John', None).full_name == "Jane & John"
assert Contact('Bart', 'Simpson', 'Lisa', 'Simpson').full_name == "Bart & Lisa Simpson"
assert Contact('Bart', 'Simpson', 'Frodo', 'Baggins').full_name == "Bart Simpson & Frodo Baggins"
assert Contact('Bart', None, None, None).full_name == "Bart"
assert Contact('Bart', 'Simpson', None, None).full_name == "Bart Simpson"
assert Contact(None, 'Simpson', None, None).full_name == "Simpson"
assert Contact(None, None, None, None).full_name == ""
# correct?
assert Contact('Bart', 'Simpson', 'Lisa', None).full_name == "Bart Simpson & Lisa"
# correct??
assert Contact('Bart', None, 'Lisa', 'Simpson').full_name == "Bart & Lisa Simpson"
Then it's just a question of dividing the problem into smaller pieces, I've put everything into a regular class just to make it easier to test. First some helper methods:
class Contact(object):
def __init__(self, a, b, c, d):
self.first_name1 = a
self.last_name1 = b
self.first_name2 = c
self.last_name2 = d
def combined_last_name(self, a, b):
"Return ``a`` if ``a`` and ``b`` are equal, otherwise returns None."
return a if a and b and a == b else None
def normalize_name(self, n):
"Returns the name or the empty string if ``n`` is None."
return n if n else ""
def get_name(self, f, l):
"""Returns a string containing firstname lastname and omits any of them
if they're None.
"""
if f and l:
return "%s %s" % (f, l)
if f:
return f
elif l:
return l
return ""
def has_second_name(self):
"Returns true if there is a second name."
return self.first_name2 or self.last_name2
then we can define the full_name property:
#property
def full_name(self):
"""Returns a string that combines first and last names of two people
depending if the last names are the same or not. If the last name
is the same, it displays as::
first_name1 & first_name2 last_name1
"""
cln = self.combined_last_name(self.last_name1, self.last_name2)
if cln: # have a common last name..
return "%s & %s %s" % (
self.first_name1,
self.first_name2,
cln
)
elif self.has_second_name():
return "%s & %s" % (
self.get_name(self.first_name1, self.last_name1),
self.get_name(self.first_name2, self.last_name2)
)
else:
return self.get_name(self.first_name1, self.last_name1)
if we put everything in a file named fullname.py we can use the pytest tool (pip install pytest) to run the tests:
c:\srv\tmp> pytest --verbose fullname.py
============================= test session starts =============================
platform win32 -- Python 2.7.16, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 -- c:\srv\venv\finautfaktura\scripts\python.exe
cachedir: .cache
rootdir: c:\srv\tmp, inifile:
plugins: xdist-1.20.1, forked-0.2, django-3.1.2, cov-2.5.1
collected 1 item
fullname.py::test_contact_full_name PASSED [100%]
========================== 1 passed in 0.20 seconds ===========================
All is well... or is it?
Let's write another test:
def test_only_second_name():
assert Contact(None, None, None, "Simpson").full_name == "Simpson"
assert Contact(None, None, "Lisa", "Simpson").full_name == "Lisa Simpson"
assert Contact(None, None, "Lisa", None).full_name == "Lisa"
running pytest again reveals the (first) error:
c:\srv\tmp> pytest --verbose fullname.py
============================= test session starts =============================
platform win32 -- Python 2.7.16, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 -- c:\srv\venv\finautfaktura\scripts\python.exe
cachedir: .cache
rootdir: c:\srv\tmp, inifile:
plugins: xdist-1.20.1, forked-0.2, django-3.1.2, cov-2.5.1
collected 2 items
fullname.py::test_contact_full_name PASSED [ 50%]
fullname.py::test_only_second_name FAILED [100%]
================================== FAILURES ===================================
____________________________ test_only_second_name ____________________________
def test_only_second_name():
> assert Contact(None, None, None, "Simpson").full_name == "Simpson"
E AssertionError: assert ' & Simpson' == 'Simpson'
E - & Simpson
E ? ---
E + Simpson
fullname.py:83: AssertionError
===================== 1 failed, 1 passed in 0.37 seconds ======================
i.e. the property returned " & Simpson" instead of the expected "Simpson" for the first assert.
To fix this we can make the full_name property handle this added complexity as well, or.., we can solve the problem somewhere else, e.g. in the __init__:
class Contact(object):
def __init__(self, a, b, c, d):
self.first_name1 = a
self.last_name1 = b
self.first_name2 = c
self.last_name2 = d
if not a and not b:
# if no name1, then put name2 into name1 and set name2 to None
self.first_name1 = self.first_name2
self.last_name1 = self.last_name2
self.first_name2 = self.last_name2 = None
running pytest again shows that this fixed the second test.
You can of course not provide your own __init__ in a Django model to solve this problem, but you can do something similar in if you override the save(..) method:
def save(self, *args, **kwargs):
if not self.first_name1 and not self.last_name1:
self.first_name1 = self.first_name2
self.last_name1 = self.last_name2
self.first_name2 = self.last_name2 = None
super(Contact, self).save(*args, **kwargs)

Related

Manipulating instances of a class change the whole class

So I'm struggling. I've seen quite a few posts on this topic, but after a couple hours struggling with the problem, I can't figure this out. Here are a few snippets of code I have. I'd like to be able to change one instance of the class without the other being changed as well, and I'm coming up short
class voter:
def __init__(self, iPositon, jPosition, affiliation):
self.affiliation = affiliation
self.iPositon = iPositon
self.jPosition = jPosition
class district:
def __init__(self, *listOfVotersPassed):
# super(district, self).__init__()
self.listOfVoters = []
for x in listOfVotersPassed:
self.listOfVoters.append(x)
class city:
def __init__(self, *listOfDistrictsPassed):
self.listOfDistricts = []
for x in listOfDistrictsPassed:
self.listOfDistricts.append(x)
def main():
# I have a list of class district that I pass to city()
startCity = city(*startCityList)
solutionCity = city(*solutionCityList)
print solutionCity.listOfDistricts[0].listOfVoters[0].affiliation # Democrat
print startCity.listOfDistricts[0].listOfVoters[0].affiliation # Democrat
solutionCity.listOfDistricts[0].listOfVoters[0].affiliation = "Republican"
print solutionCity.listOfDistricts[0].listOfVoters[0].affiliation # Republican
print startCity.listOfDistricts[0].listOfVoters[0].affiliation # Republican
if __name__ == "__main__":
main()
edit.
So I was asked about how I created the instance(s) of city. I have a file I'm reading in, each has either R or D in each line.
file = open(fileName)
# import contents
startVoterList = []
solutionVoterList = []
for line in file:
for house in line:
if " " in house:
pass
elif "\n" in house:
pass
else:
startVoterList.append(house)
solutionVoterList.append(house)
I also updated the classes, now each is in the following format. No changes besides the "(object)" after each class className.
class voter(object):
def __init__(self, iPositon, jPosition, affiliation):
self.affiliation = affiliation
self.iPositon = iPositon
self.jPosition = jPosition

Function List Exercise: List within list not printing

I have been asked to create 3 functions to create Vet Records. I have to use 2 functions to validate the Integer input and String Input respectively. I have completed majority of the code but the final part doesn't get printed when I run the code. My code is as follows:
def getNewVetRecord():
getNewVetRecord=[]
return getNewvetRecord
def getValidInteger():
Ask_for_value = True
while(Ask_for_value == True):
try:
ID = int(input("Pet ID: "))
Ask_for_value = False
except ValueError:
print("Please enter a valid integer")
return ID
def getValidString():
Ask_for_value = False
Input_String = ("")
while(Ask_for_value == False):
Input_String = input("Pet Name: ")
Ask_for_value = Input_String.isalpha()
if(Ask_for_value == False):
print("Please enter a valid name")
return Input_String
def func():
stringValue = getValidString();
integerValue = getValidInteger();
species = input("species:")
ownername = input("owner first name:")
owner_last_name = input("owner last name:")
x=[]
x.append(stringValue)
x.append(integerValue)
x.append(species)
x.append(ownername)
x.append(owner_last_name)
return x
def main():
x=[]
x.append(func())
x.append(func())
x.append(func())
print( 'ID Pet Name Species\tOwner Name' )
print( '-- -------- -------\t----------' )
getVetRecord=[]
for x in getVetRecord:
for ind in x:
print(format(getVetRecord[1][ind], "3d"), format(getVetRecord[2][ind]+"", "11s"), format("", "1s"),\
format(getVetRecord[3][ind]+"", "10s"), format("", "2s"), getVetRecord[5][ind],',', getVetRecord[4][ind])
main()
It is doing the important part correctly, (validation and input) but it is not printing the answer.
It shows the following when I run the code

Replace all elements of a two-item list with elements from another list

I'm learning about classes in Python and decided to create one just to practice but I'm having problems when displaying the instance's attributes in a specific way:
from abc import ABCMeta, abstractmethod
class Salad(object):
__metaclass__ = ABCMeta
seasoning = ["salt", "vinegar", "olive oil"] # default seasoning
def __init__(self, name, type, difficulty_level, ingredients):
self.name = name
self.type = type
self.difficulty_level = difficulty_level
self.ingredients = ingredients
def prepare(self, extra_actions=None):
self.actions = ["Cut", "Wash"]
for i in extra_actions.split():
self.actions.append(i)
for num, action in enumerate(self.actions, 1):
print str(num) + ". " + action
def serve(self):
return "Serve with rice and meat or fish."
# now begins the tricky part:
def getSaladattrs(self):
attrs = [[k, v] for k, v in self.__dict__.iteritems() if not k.startswith("actions")] # I don't want self.actions
sortedattrs = [attrs[2],attrs[1], attrs[3], attrs[0]]
# sorted the list to get this order: Name, Type, Difficulty Level, Ingredients
keys_prettify = ["Name", "Type", "Difficulty Level", "Ingredients"]
for i in range(len(keys_prettify)):
for key in sortedattrs:
sortedattrs.replace(key[i], keys_prettify[i])
# this didn't work
#abstractmethod
def absmethod(self):
pass
class VeggieSalad(Salad):
seasoning = ["Salt", "Black Pepper"]
def serve(self):
return "Serve with sweet potatoes."
vegsalad = VeggieSalad("Veggie", "Vegetarian","Easy", ["lettuce", "carrots", "tomato", "onions"])
Basically, I'd like to get this output when calling vegsalad.getSaladattrs():
Name: Veggie
Type: Vegetarian
Difficulty Level: Easy
Ingredients: Carrots, Lettuce, Tomato, Onions
instead of this (which is what I get if I simply tell python to display the keys and values using a for loop):
name: Veggie
type: Vegetarian
difficulty_level: Easy
ingredients: lettuce, carrots, tomato, onions
Thanks in advance!
Your list of attributes and values seems to be of the form:
[['name', 'Veggie'], ['type', 'Vegetarian'], ['difficulty_level', 'Easy'], ['Ingredients', 'Carrots, Lettuce, Tomato, Onions' ]]
So the following should produce the output you want:
for e in attrs:
if '_' in e[0]:
print e[0][:e[0].find('_')].capitalize() + ' ' \
+ e[0][e[0].find('_') + 1:].capitalize() + ': ' + e[1]
else:
print e[0].capitalize() + ': ' + e[1]

why my function doesn't remove objects from final_list?

hey guys:)im sorry for all the code but i feel its necessary for u to see everything..
i tried everything... i hidden prints in the code, debugging for ten times, triple checked the built in methods, and still, the .crawl() method dosnt remove any object from the final_list.
the object of my assignment is to built two classes:
Web_page : holds data of a web page.(the pages come in the form of html files saved in a folder on my desktop. Crawler: compare between pages and hold a list of the uniqe pages---> final_list
import re
import os
def remove_html_tags(s):
tag = False
quote = False
out = ""
for c in s:
if c == '<' and not quote:
tag = True
elif c == '>' and not quote:
tag = False
elif (c == '"' or c == "'") and tag:
quote = not quote
elif not tag:
out = out + c
return out
def lev(s1, s2):
return lev_iter(s1, s2, dict())
def lev_iter(s1, s2, mem):
(i,j) = (len(s1), len(s2))
if (i,j) in mem:
return mem[(i,j)]
s1_low = s1.lower()
s2_low = s2.lower()
if len(s1_low) == 0 or len(s2_low) == 0:
return max(len(s1_low), len(s2_low))
d1 = lev_iter(s1_low[:-1], s2_low, mem) + 1
d2 = lev_iter(s1_low, s2_low[:-1], mem) + 1
last = 0 if s1_low[-1] == s2_low[-1] else 1
d3 = lev_iter(s1_low[:-1], s2_low[:-1], mem) + last
result = min(d1, d2, d3)
mem[(i,j)] = result
return result
def merge_spaces(content):
return re.sub('\s+', ' ', content).strip()
""" A Class that holds data on a Web page """
class WebPage:
def __init__(self, filename):
self.filename = filename
def process(self):
f = open(self.filename,'r')
LINE_lst = f.readlines()
self.info = {}
for i in range(len(LINE_lst)):
LINE_lst[i] = LINE_lst[i].strip(' \n\t')
LINE_lst[i] = remove_html_tags(LINE_lst[i])
lines = LINE_lst[:]
for line in lines:
if len(line) == 0:
LINE_lst.remove(line)
self.body = ' '.join(LINE_lst[1:])
self.title = LINE_lst[0]
f.close()
def __str__(self):
return self.title + '\n' + self.body
def __repr__(self):
return self.title
def __eq__(self,other):
n = lev(self.body,other.body)
k = len(self.body)
m = len(other.body)
return float(n)/max(k,m) <= 0.15
def __lt__(self,other):
return self.title < other.title
""" A Class that crawls the web """
class Crawler:
def __init__(self, directory):
self.folder = directory
def crawl(self):
pages = [f for f in os.listdir(self.folder) if f.endswith('.html')]
final_list = []
for i in range(len(pages)):
pages[i] = WebPage(self.folder + '\\' + pages[i])
pages[i].process()
for k in range(len(final_list)+1):
if k == len(final_list):
final_list.append(pages[i])
elif pages[i] == final_list[k]:
if pages[i] < final_list[k]:
final_list.append(pages[i])
final_list.remove(final_list[k])
break
print final_list
self.pages = final_list
everything works fine besides this freaking line final_list.remove(final_list[k]). help please? whats wrong here?
I'm not sure why your code doesn't work, it's difficult to test it because I don't know what kind of input should end up calling remove().
I suggest following these steps:
Make sure that remove() is called at some point.
remove() relies on your __eq__() method to find the item to remove, so make sure that __eq__() isn't the culprit.
As a side note, you will probably want to replace this:
self.folder + '\\' + pages[i]
with:
import os.path
# ...
os.path.join(self.folder, page[i])
This simple change should make your script work on all operating systems, rather than on Windows only. (GNU/Linux, Mac OS and other Unix-like OS use “/” as path separator.)
Please also consider replacing loops of this form:
for i in range(len(sequence)):
# Do something with sequence[i]
with:
for item in sequence:
# Do something with item
If you need the item index, use enumerate():
for i, item in enumerate(sequence):

Display tagged list as indented tree grid

I am writing a logger which records the level of the entries.
To make it simple, let's say it logs entries like <level> <message>.
I am now trying to write a log viewer which formats the logfile "nicely" as an indented tree grid.
For example is the raw log file contains:
0 entry1
0 entry2
1 entry3
2 entry4
3 entry5
2 entry6
0 entry7
It should output:
entry1
entry2
└entry3
├entry4
│└entry5
└entry6
entry7
My first steps were
Converting the list into a tree
Recursively print the tree
This worked with one single exception: I cannot figure out how I can pass the information that - referring to the example - before entry5 comes the │ sign to display that the previous level continues after the sub-levels.
So any hint, how to come from the list to the desired output is welcome.
Finally got it:
class LogViewer(LogFile):
"""
Formats raw log file contents nicely
and thus makes it human-readable
"""
__down = False
class EntryTreeNode():
"""
A minimal entry wrapper
"""
def __init__(self, string):
"""
Constructor
"""
lst = string.split(LogEntry.colsep())
if len(lst) != 6:
raise Exception('Invalid entry: ' + string)
else:
self.DATE = datetime.strptime(lst[0], LogEntry.timeformat())
self.ERRLVL = ErrLvlType(lst[1])
self.USER = lst[2]
self.CALLER = lst[3]
self.OFFSET = int(lst[4])
self.MSG = lst[5]
self.tag = self.OFFSET
self.children = []
self.pre = '[' + datetime.strftime(self.DATE, LogEntry.timeformat()) + ']\t' \
+ str(self.ERRLVL) + '\t' \
+ str(self.USER) + '\t'
self.post = str(self.CALLER) + ' \t' + str(self.MSG)
def __repr__(self):
return str(self.tag)
def __init__(self, path):
"""
Constructor
"""
super().__init__(path)
#property
def __sym_last(self):
"""
Returns the symbol for a last entry
"""
return '┌' if self.__down else '└'
#property
def __sym_mid(self):
"""
Returns the symbol for a middle entry
"""
return '├'
#property
def __sym_follow(self):
"""
Returns the symbol for a following entry
"""
return '│'
def __mktree(self, lst):
"""
Converts a log entry list into a tree
"""
roots = []
def children(root, lst):
result = []
while lst:
curr = lst.pop()
if curr.tag == root.tag + 1:
curr.children = children(curr, lst)
result.append(curr)
else:
lst.append(curr)
break
return result
while lst:
curr = lst.pop()
if curr.tag == 0:
curr.children = children(curr, lst)
roots.append(curr)
return roots
def __print_tree(self, root, offset='', prefix='', last=True):
"""
Prints a log entry tree
"""
print(root.pre + offset + prefix + root.post)
if last:
offset += ' '
else:
offset += self.__sym_follow
for i in range(0, len(root.children)):
if i == len(root.children)-1:
prefix = self.__sym_last
last = True
else:
prefix = self.__sym_mid
last = False
self.__print_tree(root.children[i], offset, prefix, last)
def display(self, reverse=False):
"""
Displays the log file nicely
"""
self.__down = reverse
entries = reversed(self.dump()) if reverse else self.dump()
entries = [self.EntryTreeNode(e) for e in entries]
tree = self.__mktree(entries)
for root in tree:
self.__print_tree(root)