All Fields of Django Model Entry Comma Separated - python

The code in the two if-blocks smells to violate DRY. How can it be written more generic?
selected_class = eval(choice) # bad (see comments)
selected_class = getattr(models, choice) # good (see comments)
records = selected_class.objects.all()
if (choice == 'Treatment'):
for record in records:
response.write(str(record.id) + ',' + str(record.available_hours) + '\n')
if (choice == 'Patient'):
for record in records:
response.write(str(record.id) + ',' + record.first_name + '\n')
I could write in each model (Treatment and Patient) a method 'make_csv'. But, there must be a better way.

A simple solution:
for record in records:
if choice == 'Treatment':
item = str(record.available_hours)
elif choice == 'Patient':
item = record.first_name
response.write('{},{}\n'.format(record.id, item))
Or, if you want a slightly more complex solution that avoids repeating the if:
choices_dict = {
'Treatment': 'available_hours',
'Patient': 'first_name',
}
record_field = choices_dict[choice]
for record in records:
item = getattr(record, record_field)
response.write('{},{}\n'.format(record.id, item))
It's also more flexible in case you may want to change or add options to choices_dict, but that may not be relevant.

Related

How to call a view from within itself in a Django REST API?

I have this function in my Django REST API that handles the insertion of products. Sometimes a product will be a variational product (when product.type = 3), in which case I get all the permutations of these variations and want to insert a new product corresponding to each permutation into the database.
#api_view(['POST'])
def upsertProduct(request):
productData = request.data['product']
variations = productData.pop('variations', [])
variationTypes = productData.pop('variationTypes', [])
product, created = Product.objects.update_or_create(pk=productData['id'], defaults=productData)
if (productData['type'].id == 3):
variations_by_type = []
for variationType in variationTypes:
variations_by_type.append([variation for variation in variations if variation['variationType'] == variationType['id']])
combinations = list(itertools.product(*variations_by_type))
for combination in combinations:
productData['name'] = product.name + ' (' + ' | '.join(' : '.join(i['name'] for i in item) for item in combination) + ')'
productData['type'] = {'id': 5}
productData['parent'] = product.id
#Recursive call should go here
#upsertProduct(request={'data': {'product': productData}})
My first attempt was to simply call the function as I did in the commented line. Django returned the following
AssertionError: The request argument must be an instance of
django.http.HttpRequest, not builtins.dict.
I then attempted making use of this HttpRequest object from Django, but can not figure out how to use it properly for this use case. I've also tried the standard request library in Python, without success, and I am also pretty sure that wouldn't be an ideal solution even if it did work.
In general, I wouldn't create directly a HttpRequest object that would be used by a view somewhere else, because it contains much more information than the payload data, such as session, user, middleware, context info, etc.
What you could do in your code, is keeping the single request variable as is and make another function that handles a dictionary of data.
For example, taking what you wrote, that would give something like:
def handle_data(productData):
"""
Handle a dictionary of data
"""
product, created = Product.objects.update_or_create(pk=productData['id'], defaults=productData)
return product
#api_view(['POST'])
def upsertProduct(request):
productData = request.data['product']
variations = productData.pop('variations', [])
variationTypes = productData.pop('variationTypes', [])
product = handle_data(productData)
if (productData['type'].id == 3):
variations_by_type = []
for variationType in variationTypes:
variations_by_type.append([variation for variation in variations if variation['variationType'] == variationType['id']])
combinations = list(itertools.product(*variations_by_type))
for combination in combinations:
productData['name'] = product.name + ' (' + ' | '.join(' : '.join(i['name'] for i in item) for item in combination) + ')'
productData['type'] = {'id': 5}
productData['parent'] = product.id
handle_data(productData)

using another WS as validation Flask/Rest/Mysql

I am trying to build a simple web application with 3 web services. Two of my web services are supposed to validate if a student exist in a course or not. This is done by a simple SELECT-query. My third web service should add a student into a database, but only if the student do exist in the specific course.
This is my validation WS which should return a true/false.
#app.route('/checkStudOnCourse/<string:AppCode>/<string:ideal>', methods= ["GET"])
def checkStudOnCourseWS(AppCode, ideal):
myCursor3 = mydb.cursor()
query3 = ("SELECT studentID FROM Ideal.course WHERE applicationCode = " + "'" + AppCode + "' AND Ideal = " + "'" + ideal + "'")
myCursor3.execute(query3)
myresult3 = myCursor3.fetchall()
if len(myresult3) == 0:
return render_template('Invalid.html')
else:
return jsonify({'Student in course ': True})
Below is regResult which should do a SQL insert into a database. I only want the submit to work if the above result is "True", how can I do that? I know I have not done the INSERT query, but that is not a problem.
What I am unsure about is: How can I only let the submit be be INSERTED if the validation WS is "True".
#app.route('/register', methods=["POST", "GET"])
def regResultat():
if request.method == "POST":
Period = request.form['period']
#ProvNr = request.form['provNr']
Grade = request.form['grade']
Applicationcode = request.form['applicationcode']
#Datum = request.form['datum']
Ideal = request.form['ideal']
CheckStudOnCourse = 'http://127.0.0.1:5000/checkAppCodeWS/'+Applicationcode+'/'+Ideal
CheckStudOnResp = requests.get(CheckStudOnCourse)
At first, such syntax:
if len(myresult3) == 0, can be simplified by if myresult3, because Python evaluates that implicitly to bool.
Secondly, if you once returned from function, there is no need to write an else statement:
if len(myresult3) == 0:
return render_template('Invalid.html') # < -- in case 'True',
# it returns here, otherwise
# function keeps going"""
return jsonify({'Student in course ': True}) # < -- in case 'False', it is returned here
Focusing on your issue, you could do that:
Get your value from ws
CheckStudOnCourse = 'http://127.0.0.1:5000/checkAppCodeWS/'+Applicationcode+'/'+Ideal
CheckStudOnResp = requests.get(CheckStudOnCourse)
Extract json from it:
if result_as_json.status == 200:
result_as_json = CheckStudOnResp.json() # < -- it is now a dict
Do some checks:
if result_as_json.get('Student in course', False): # I highly suggest to use other
# convention to name json keys
# e.g. Student in course ->
# student_exists_in_course
# do your code here

PRAW Adding reddit users with 3+ comments in a subreddit to a list

I currently have a Python reddit bot written using PRAW that gets all of the comments from a specific subreddit, looks at their author and finds out if that author has at least 3 comments in the subreddit. If they have 3+ comments, then they are added to an approved to post submissions text file.
My code currently "works" but honestly, it's so bad that I'm not even sure how. What is a better way of accomplishing my goal?
What I currently have:
def get_approved_posters(reddit):
subreddit = reddit.subreddit('Enter_Subreddit_Here')
subreddit_comments = subreddit.comments(limit=1000)
dictionary_of_posters = {}
unique_posters = {}
commentCount = 1
duplicate_check = False
unique_authors_file = open("unique_posters.txt", "w")
print("Obtaining comments...")
for comment in subreddit_comments:
dictionary_of_posters[commentCount] = str(comment.author)
for key, value in dictionary_of_posters.items():
if value not in unique_posters.values():
unique_posters[key] = value
for key, value in unique_posters.items():
if key >= 3:
commentCount += 1
if duplicate_check is not True:
commentCount += 1
print("Adding author to dictionary of posters...")
unique_posters[commentCount] = str(comment.author)
print("Author added to dictionary of posters.")
if commentCount >= 3:
duplicate_check = True
for x in unique_posters:
unique_authors_file.write(str(unique_posters[x]) + '\n')
total_comments = open("total_comments.txt", "w")
total_comments.write(str(dictionary_of_posters))
unique_authors_file.close()
total_comments.close()
unique_authors_file = open("unique_posters.txt", "r+")
total_comments = open("total_comments.txt", "r")
data = total_comments.read()
approved_list = unique_authors_file.read().split('\n')
print(approved_list)
approved_posters = open("approved_posters.txt", "w")
for username in approved_list:
count = data.count(username)
if(count >= 3):
approved_posters.write(username + '\n')
print("Count for " + username + " is " + str(count))
approved_posters.close()
unique_authors_file.close()
total_comments.close()
Maybe it's just me being slow this morning, but I'm struggling to follow/understand your use of commentCount and unique_posters. Actually, it probably is me.
I would get all the comments from the subreddit, like you did, and for each comment, do the following:
for comment in subreddit_comments:
try:
dictionary_of_posters[comment.author] += 1
except KeyError:
dictionary_of_posters[comment.author] = 1
for username, comment_count in dictionary_of_posters.items():
if comment_count >= 3:
approved_authors.append(username)
This method takes advantage of the fact that dictionaries cannot have two of the same key value. That way, you don't have to do a duplicate check or anything. If it makes you feel better, you can go list(set(approved_authors)) and that will get rid of any stray duplicates.

Reduce RAM usage in Python script

I've written a quick little program to scrape book data off of a UNESCO website which contains information about book translations. The code is doing what I want it to, but by the time it's processed about 20 countries, it's using ~6GB of RAM. Since there are around 200 I need to process, this isn't going to work for me.
I'm not sure where all the RAM usage is coming from, so I'm not sure how to reduce it. I'm assuming that it's the dictionary that's holding all the book information, but I'm not positive. I'm not sure if I should simply make the program run once for each country, rather than processing the lot of them? Or if there's a better way to do it?
This is the first time I've written anything like this, and I'm a pretty novice, self-taught programmer, so please point out any significant flaws in the code, or improvement tips you have that may not directly relate to the question at hand.
This is my code, thanks in advance for any assistance.
from __future__ import print_function
import urllib2, os
from bs4 import BeautifulSoup, SoupStrainer
''' Set list of countries and their code for niceness in explaining what
is actually going on as the program runs. '''
countries = {"AFG":"Afghanistan","ALA":"Aland Islands","DZA":"Algeria"}
'''List of country codes since dictionaries aren't sorted in any
way, this makes processing easier to deal with if it fails at
some point, mid run.'''
country_code_list = ["AFG","ALA","DZA"]
base_url = "http://www.unesco.org/xtrans/bsresult.aspx?lg=0&c="
destination_directory = "/Users/robbie/Test/"
only_restable = SoupStrainer(class_="restable")
class Book(object):
def set_author(self,book):
'''Parse the webpage to find author names. Finds last name, then
first name of original author(s) and sets the Book object's
Author attribute to the resulting string.'''
authors = ""
author_last_names = book.find_all('span',class_="sn_auth_name")
author_first_names = book.find_all('span', attrs={\
'class':"sn_auth_first_name"})
if author_last_names == []: self.Author = [" "]
for author in author_last_names:
try:
first_name = author_first_names.pop()
authors = authors + author.getText() + ', ' + \
first_name.getText()
except IndexError:
authors = authors + (author.getText())
self.author = authors
def set_quality(self,book):
''' Check to see if book page is using Quality, then set it if
so.'''
quality = book.find_all('span', class_="sn_auth_quality")
if len(quality) == 0: self.quality = " "
else: self.quality = quality[0].contents[0]
def set_target_title(self,book):
target_title = book.find_all('span', class_="sn_target_title")
if len(target_title) == 0: self.target_title = " "
else: self.target_title = target_title[0].contents[0]
def set_target_language(self,book):
target_language = book.find_all('span', class_="sn_target_lang")
if len(target_language) == 0: self.target_language = " "
else: self.target_language = target_language[0].contents[0]
def set_translator_name(self,book) :
translators = ""
translator_last_names = book.find_all('span', class_="sn_transl_name")
translator_first_names = book.find_all('span', \
class_="sn_transl_first_name")
if translator_first_names == [] and translator_last_names == [] :
self.translators = " "
return None
for translator in translator_last_names:
try:
first_name = translator_first_names.pop()
translators = translators + \
(translator.getText() + ',' \
+ first_name.getText())
except IndexError:
translators = translators + \
(translator.getText())
self.translators = translators
def set_published_city(self,book) :
published_city = book.find_all('span', class_="place")
if len(published_city) == 0:
self.published_city = " "
else: self.published_city = published_city[0].contents[0]
def set_publisher(self,book) :
publisher = book.find_all('span', class_="place")
if len(publisher) == 0:
self.publisher = " "
else: self.publisher = publisher[0].contents[0]
def set_published_country(self,book) :
published_country = book.find_all('span', \
class_="sn_country")
if len(published_country) == 0:
self.published_country = " "
else: self.published_country = published_country[0].contents[0]
def set_year(self,book) :
year = book.find_all('span', class_="sn_year")
if len(year) == 0:
self.year = " "
else: self.year = year[0].contents[0]
def set_pages(self,book) :
pages = book.find_all('span', class_="sn_pagination")
if len(pages) == 0:
self.pages = " "
else: self.pages = pages[0].contents[0]
def set_edition(self, book) :
edition = book.find_all('span', class_="sn_editionstat")
if len(edition) == 0:
self.edition = " "
else: self.edition = edition[0].contents[0]
def set_original_title(self,book) :
original_title = book.find_all('span', class_="sn_orig_title")
if len(original_title) == 0:
self.original_title = " "
else: self.original_title = original_title[0].contents[0]
def set_original_language(self,book) :
languages = ''
original_languages = book.find_all('span', \
class_="sn_orig_lang")
for language in original_languages:
languages = languages + language.getText() + ', '
self.original_languages = languages
def export(self, country):
''' Function to allow us to easilly pull the text from the
contents of the Book object's attributes and write them to the
country in which the book was published's CSV file.'''
file_name = os.path.join(destination_directory + country + ".csv")
with open(file_name, "a") as by_country_csv:
print(self.author.encode('UTF-8') + " & " + \
self.quality.encode('UTF-8') + " & " + \
self.target_title.encode('UTF-8') + " & " + \
self.target_language.encode('UTF-8') + " & " + \
self.translators.encode('UTF-8') + " & " + \
self.published_city.encode('UTF-8') + " & " + \
self.publisher.encode('UTF-8') + " & " + \
self.published_country.encode('UTF-8') + " & " + \
self.year.encode('UTF-8') + " & " + \
self.pages.encode('UTF-8') + " & " + \
self.edition.encode('UTF-8') + " & " + \
self.original_title.encode('UTF-8') + " & " + \
self.original_languages.encode('UTF-8'), file=by_country_csv)
by_country_csv.close()
def __init__(self, book, country):
''' Initialize the Book object by feeding it the HTML for its
row'''
self.set_author(book)
self.set_quality(book)
self.set_target_title(book)
self.set_target_language(book)
self.set_translator_name(book)
self.set_published_city(book)
self.set_publisher(book)
self.set_published_country(book)
self.set_year(book)
self.set_pages(book)
self.set_edition(book)
self.set_original_title(book)
self.set_original_language(book)
def get_all_pages(country,base_url):
''' Create a list of URLs to be crawled by adding the ISO_3166-1_alpha-3
country code to the URL and then iterating through the results every 10
pages. Returns a string.'''
base_page = urllib2.urlopen(base_url+country)
page = BeautifulSoup(base_page, parse_only=only_restable)
result_number = page.find_all('td',class_="res1",limit=1)
if not result_number:
return 0
str_result_number = str(result_number[0].getText())
results_total = int(str_result_number.split('/')[1])
page.decompose()
return results_total
def build_list(country_code_list, countries):
''' Build the list of all the books, and return a list of Book objects
in case you want to do something with them in something else, ever.'''
for country in country_code_list:
print("Processing %s now..." % countries[country])
results_total = get_all_pages(country, base_url)
for url in range(results_total):
if url % 10 == 0 :
all_books = []
target_page = urllib2.urlopen(base_url + country \
+"&fr="+str(url))
page = BeautifulSoup(target_page, parse_only=only_restable)
books = page.find_all('td',class_="res2")
for book in books:
all_books.append(Book (book,country))
page.decompose()
for title in all_books:
title.export(country)
return
if __name__ == "__main__":
build_list(country_code_list,countries)
print("Completed.")
I guess I'll just list off some of the problems or possible improvements in no particular order:
Follow PEP 8.
Right now, you've got lots of variables and functions named using camel-case like setAuthor. That's not the conventional style for Python; Python would typically named that set_author (and published_country rather than PublishedCountry, etc.). You can even change the names of some of the things you're calling: for one, BeautifulSoup supports findAll for compatibility, but find_all is recommended.
Besides naming, PEP 8 also specifies a few other things; for example, you'd want to rewrite this:
if len(resultNumber) == 0 : return 0
as this:
if len(result_number) == 0:
return 0
or even taking into account the fact that empty lists are falsy:
if not result_number:
return 0
Pass a SoupStrainer to BeautifulSoup.
The information you're looking for is probably in only part of the document; you don't need to parse the whole thing into a tree. Pass a SoupStrainer as the parse_only argument to BeautifulSoup. This should reduce memory usage by discarding unnecessary parts early.
decompose the soup when you're done with it.
Python primarily uses reference counting, so removing all circular references (as decompose does) should let its primary mechanism for garbage collection, reference counting, free up a lot of memory. Python also has a semi-traditional garbage collector to deal with circular references, but reference counting is much faster.
Don't make Book.__init__ write things to disk.
In most cases, I wouldn't expect just creating an instance of a class to write something to disk. Remove the call to export; let the user call export if they want it to be put on the disk.
Stop holding on to so much data in memory.
You're accumulating all this data into a dictionary just to export it afterwards. The obvious thing to do to reduce memory is to dump it to disk as soon as possible. Your comment indicates that you're putting it in a dictionary to be flexible; but that doesn't mean you have to collect it all in a list: use a generator, yielding items as you scrape them. Then the user can iterate over it just like a list:
for book in scrape_books():
book.export()
…but with the advantage that at most one book will be kept in memory at a time.
Use the functions in os.path rather than munging paths yourself.
Your code right now is rather fragile when it comes to path names. If I accidentally removed the trailing slash from destinationDirectory, something unintended happens. Using os.path.join prevents that from happening and deals with cross-platform differences:
>>> os.path.join("/Users/robbie/Test/", "USA")
'/Users/robbie/Test/USA'
>>> os.path.join("/Users/robbie/Test", "USA") # still works!
'/Users/robbie/Test/USA'
>>> # or say we were on Windows:
>>> os.path.join(r"C:\Documents and Settings\robbie\Test", "USA")
'C:\\Documents and Settings\\robbie\\Test\\USA'
Abbreviate attrs={"class":...} to class_=....
BeautifulSoup 4.1.2 introduces searching with class_, which removes the need for the verbose attrs={"class":...}.
I imagine there are even more things you can change, but that's quite a few to start with.
What do you want the booklist for, in the end? You should export each book at the end of the "for url in range" block (inside it), and do without the allbooks dict. If you really need a list, define exactly what infos you will need, not keeping full Book objects.

Python for looping to while looping

I need some help with converting the below code into something a bit more manageable.
I'm pretty sure I need modify it to include some while statements. But have been hitting my head against a wall for the last day or so. I think I'm close....
for LevelItemList[1] in LevelUrlList[1]:
if LevelItemList[1][1] == "Folder":
printFolderHeader(1,LevelItemList[1][0])
LevelUrlList[2] = parseHTML (LevelItemList[1][2])
for LevelItemList[2] in LevelUrlList[2]:
if LevelItemList[2][1] == "Folder":
printFolderHeader(2,LevelItemList[2][0])
LevelUrlList[3] = parseHTML (LevelItemList[2][2])
for LevelItemList[3] in LevelUrlList[3]:
if LevelItemList[3][1] == "Folder":
printFolderHeader(3,LevelItemList[3][0])
LevelUrlList[4] = parseHTML (LevelItemList[3][2])
for LevelItemList[4] in LevelUrlList[4]:
if LevelItemList[4][1] == "Folder":
printFolderHeader(4,LevelItemList[4][0])
LevelUrlList[5] = parseHTML (LevelItemList[4][2])
for LevelItemList[5] in LevelUrlList[5]:
if LevelItemList[5][1] == "Folder":
printFolderHeader(5,LevelItemList[5][0])
LevelUrlList[6] = parseHTML (LevelItemList[5][2])
for LevelItemList[6] in LevelUrlList[6]:
printPage(6,LevelItemList[6][0])
printFolderFooter(5,LevelItemList[5][0])
else:
printPage(5,LevelItemList[5][0])
printFolderFooter(4,LevelItemList[4][0])
else:
printPage(4,LevelItemList[4][0])
printFolderFooter(3,LevelItemList[3][0])
else:
printPage(3,LevelItemList[3][0])
printFolderFooter(2,LevelItemList[2][0])
else:
printPage(2,LevelItemList[2][0])
printFolderFooter(1,LevelItemList[1][0])
else:
printPage(1,LevelItemList[1][0])
I don't have the full context of the code, but I think you can reduce it down to something like this:
def printTheList(LevelItemList, index):
for item in LevelItemList:
if item[1] == "Folder":
printFolderHeader(index,item[0])
printTheList(parseHTML (item[2]), index + 1) # note the + 1
printFolderFooter(index,item[0])
else:
printPage(index,item[0])
# and the initial call looks like this.
printTheList(LevelUrlList[1], 1)
This code makes the assumption that you don't actually need to assign the values into LevelUrlList and LevelItemList the way you are doing in your code. If you do need that data later, I suggest passing in a different data structure to hold the resulting values.

Categories